mirror of
https://github.com/python-telegram-bot/python-telegram-bot.git
synced 2026-06-19 15:45:13 +00:00
Compare commits
40 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a41d2ba85 | |||
| 108bfaf888 | |||
| 3fd6932c2d | |||
| 22c31b187a | |||
| dd8c60fa76 | |||
| 5bc0f4f5b4 | |||
| 80f3ccdcfa | |||
| 537693f082 | |||
| 72e8ded8cf | |||
| a48b08fb20 | |||
| ca2a834091 | |||
| 66e4103318 | |||
| 0cceafcab3 | |||
| fb5234d9f5 | |||
| 6665c147d2 | |||
| b18e46a80d | |||
| f85da33619 | |||
| cd015737eb | |||
| 330d2c2b99 | |||
| 0ae9f7b6c4 | |||
| cb239e7b1e | |||
| cec4a6fe6f | |||
| 5660fd8a16 | |||
| c19cd7b7bd | |||
| 48f8907882 | |||
| f385a5e769 | |||
| f18ab3a62a | |||
| 4bf77904fb | |||
| 5dec05e0c3 | |||
| 3b4426fb82 | |||
| 89911bf708 | |||
| 2e7ec1d8db | |||
| 8844fb3b2f | |||
| b82b25feeb | |||
| 0653b52222 | |||
| 3df3e6a534 | |||
| dcf7cc4091 | |||
| c851d4360f | |||
| 6b42bb83d2 | |||
| dc587ade7e |
@@ -37,7 +37,7 @@ Setting things up
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pre-commit install
|
||||
$ prek install -f
|
||||
|
||||
Finding something to do
|
||||
=======================
|
||||
@@ -100,7 +100,7 @@ Here's how to make a one-off code change.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ pre-commit run -a
|
||||
$ prek run -a
|
||||
|
||||
- To actually make the commit (this will trigger tests style & type checks automatically):
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ The repository follows a standard structure for Python projects. Here are some k
|
||||
- Read the stability guide mentioned at docs/source/stability_policy.rst to understand if your changes
|
||||
are breaking or incompatible.
|
||||
- Try to make sure your code is asyncio-friendly and thread-safe.
|
||||
- Run `uv run pre-commit` to run pre-commit hooks before committing your changes, but after `git add`ing them.
|
||||
- Run `uv run prek` to run pre-commit hooks before committing your changes, but after `git add`ing them.
|
||||
- Make sure you always test your changes. Either update or write new tests in the `tests/` directory.
|
||||
|
||||
### Pull Requests:
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Config file for workflows/labelling.yml
|
||||
|
||||
version: 1
|
||||
|
||||
labels:
|
||||
- label: "⚙️ dependencies"
|
||||
authors: ["dependabot[bot]", "pre-commit-ci[bot]"]
|
||||
- label: "🛠 code-quality"
|
||||
authors: ["pre-commit-ci[bot]"]
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
IS_RELEASE_PR: ${{ steps.check_title.outputs.IS_RELEASE_PR }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
# needed for commit and push step at the end
|
||||
persist-credentials: true
|
||||
@@ -48,7 +48,7 @@ jobs:
|
||||
# Run `chango release` if applicable - needs some additional setup.
|
||||
- name: Set up Python
|
||||
if: steps.check_title.outputs.IS_RELEASE_PR == 'true'
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
- name: Commit & Push
|
||||
if: steps.check_title.outputs.IS_RELEASE_PR == 'true'
|
||||
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
|
||||
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
||||
with:
|
||||
commit_message: "Do chango Release"
|
||||
repository: ./target-repo
|
||||
|
||||
@@ -26,15 +26,15 @@ jobs:
|
||||
# If you do not check out your code, Copilot will do this for you.
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
|
||||
with:
|
||||
# Install a specific version of uv.
|
||||
version: "0.9.13"
|
||||
version: "0.10.10"
|
||||
# Install 3.13:
|
||||
python-version: 3.13
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Test Admonitions Generation
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- src/telegram/**
|
||||
- docs/**
|
||||
@@ -24,11 +25,11 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
|
||||
@@ -19,11 +19,11 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Upload linkcheck output
|
||||
# Run also if the previous steps failed
|
||||
if: always()
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: linkcheck-output
|
||||
path: docs/build/html/output.*
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
|
||||
uses: astral-sh/setup-uv@e06108dd0aef18192324c70427afc47652e63a82 # v7.5.0
|
||||
- name: Run zizmor
|
||||
run: uvx zizmor --persona=pedantic --format sarif . > results.sarif
|
||||
env:
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
name: PR Labeler
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
pre-commit-ci:
|
||||
permissions:
|
||||
contents: read # for srvaroa/labeler to read config file
|
||||
pull-requests: write # for srvaroa/labeler to add labels in PR
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: srvaroa/labeler@0a20eccb8c94a1ee0bed5f16859aece1c45c3e55 # v1.13.0
|
||||
# Config file at .github/labeler.yml
|
||||
env:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
@@ -0,0 +1,26 @@
|
||||
name: Prek checks
|
||||
permissions:
|
||||
contents: read # Needed to see what files to run pre-commit on
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- '**' # Matches all branch names, for PRs
|
||||
push:
|
||||
branches:
|
||||
- 'master' # Run tests on master branch
|
||||
|
||||
# Cancel any in-progress runs of this workflow for the same PR or branch when a new commit is pushed.
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prek:
|
||||
name: prek
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: j178/prek-action@0bb87d7f00b0c99306c8bcb8b8beba1eb581c037 # v1.1.1
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install pypa/build
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: python3 -m build
|
||||
- name: Store the distribution packages
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
@@ -74,7 +74,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
@@ -86,13 +86,13 @@ jobs:
|
||||
sha1sum $file > $file.sha1
|
||||
done
|
||||
- name: Sign the dists with Sigstore
|
||||
uses: sigstore/gh-action-sigstore-python@f832326173235dcb00dd5d92cd3f353de3188e6c # v3.1.0
|
||||
uses: sigstore/gh-action-sigstore-python@a5caf349bc536fbef3668a10ed7f5cd309a4b53d # v3.2.0
|
||||
with:
|
||||
inputs: >-
|
||||
./dist/*.tar.gz
|
||||
./dist/*.whl
|
||||
- name: Store the distribution packages and signatures
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: python-package-distributions-and-signatures
|
||||
path: dist/
|
||||
@@ -110,11 +110,11 @@ jobs:
|
||||
actions: read # for downloading artifacts
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: python-package-distributions-and-signatures
|
||||
path: dist/
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
permissions: {}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Publish to Telegram Channel
|
||||
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
actions: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install pypa/build
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Build a binary wheel and a source tarball
|
||||
run: python3 -m build
|
||||
- name: Store the distribution packages
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: python-package-distributions
|
||||
path: dist/
|
||||
@@ -88,13 +88,13 @@ jobs:
|
||||
sha1sum $file > $file.sha1
|
||||
done
|
||||
- name: Sign the dists with Sigstore
|
||||
uses: sigstore/gh-action-sigstore-python@f832326173235dcb00dd5d92cd3f353de3188e6c # v3.1.0
|
||||
uses: sigstore/gh-action-sigstore-python@a5caf349bc536fbef3668a10ed7f5cd309a4b53d # v3.2.0
|
||||
with:
|
||||
inputs: >-
|
||||
./dist/*.tar.gz
|
||||
./dist/*.whl
|
||||
- name: Store the distribution packages and signatures
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: python-package-distributions-and-signatures
|
||||
path: dist/
|
||||
@@ -112,11 +112,11 @@ jobs:
|
||||
actions: read # for downloading artifacts
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Download all the dists
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
name: python-package-distributions-and-signatures
|
||||
path: dist/
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
# For adding labels and closing
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
|
||||
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
|
||||
with:
|
||||
# PRs never get stale
|
||||
days-before-stale: 3
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Bot API Tests
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- src/telegram/**
|
||||
- tests/**
|
||||
@@ -23,11 +24,11 @@ jobs:
|
||||
os: [ubuntu-latest]
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Check Type Completeness
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- src/telegram/**
|
||||
- pyproject.toml
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
name: Unit Tests
|
||||
on:
|
||||
pull_request:
|
||||
types: [synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- src/telegram/**
|
||||
- tests/**
|
||||
@@ -25,11 +26,11 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
fail-fast: False
|
||||
steps:
|
||||
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: 'pip'
|
||||
@@ -86,14 +87,14 @@ jobs:
|
||||
.test_report_optionals_junit.xml
|
||||
|
||||
- name: Submit coverage
|
||||
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
with:
|
||||
env_vars: OS,PYTHON
|
||||
name: ${{ matrix.os }}-${{ matrix.python-version }}
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
- name: Upload test results to Codecov
|
||||
uses: codecov/test-results-action@47f89e9acb64b76debcd5ea40642d25a4adced9f # v1.1.1
|
||||
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
files: .test_report_no_optionals_junit.xml,.test_report_optionals_junit.xml
|
||||
|
||||
+8
-13
@@ -1,14 +1,6 @@
|
||||
ci:
|
||||
autofix_prs: false
|
||||
# We use Renovate to update this file now, but we can't disable automatic pre-commit updates
|
||||
# when using the `pre-commit` GitHub Action, so we set the schedule to quarterly to avoid
|
||||
# frequent updates.
|
||||
autoupdate_schedule: quarterly
|
||||
autoupdate_commit_msg: 'Bump `pre-commit` Hooks to Latest Versions'
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: 'v0.14.13'
|
||||
rev: 'v0.15.6'
|
||||
hooks:
|
||||
# Run the linter:
|
||||
- id: ruff-check
|
||||
@@ -17,7 +9,7 @@ repos:
|
||||
- id: ruff-format
|
||||
name: ruff format
|
||||
- repo: https://github.com/PyCQA/pylint
|
||||
rev: v4.0.4
|
||||
rev: v4.0.5
|
||||
hooks:
|
||||
- id: pylint
|
||||
files: ^(?!(tests|docs)).*\.py$
|
||||
@@ -26,9 +18,10 @@ repos:
|
||||
- httpx~=0.27
|
||||
- tornado~=6.4
|
||||
- APScheduler>=3.10.4,<3.12.0
|
||||
- cachetools>=5.3.3,<6.3.0
|
||||
- cachetools>=7.0.0,<8.0.0
|
||||
- aiolimiter~=1.1,<1.3
|
||||
- . # this basically does `pip install -e .`
|
||||
priority: 10
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.19.1
|
||||
hooks:
|
||||
@@ -43,9 +36,10 @@ repos:
|
||||
- httpx~=0.27
|
||||
- tornado~=6.4
|
||||
- APScheduler>=3.10.4,<3.12.0
|
||||
- cachetools>=5.3.3,<6.3.0
|
||||
- cachetools>=7.0.0,<8.0.0
|
||||
- aiolimiter~=1.1,<1.3
|
||||
- . # this basically does `pip install -e .`
|
||||
priority: 10
|
||||
- id: mypy
|
||||
name: mypy-examples
|
||||
files: ^examples/.*\.py$
|
||||
@@ -56,5 +50,6 @@ repos:
|
||||
additional_dependencies:
|
||||
- tornado~=6.4
|
||||
- APScheduler>=3.10.4,<3.12.0
|
||||
- cachetools>=5.3.3,<6.3.0
|
||||
- cachetools>=7.0.0,<8.0.0
|
||||
- . # this basically does `pip install -e .`
|
||||
priority: 10
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ python:
|
||||
path: .
|
||||
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
os: ubuntu-24.04
|
||||
tools:
|
||||
python: "3" # latest stable cpython version
|
||||
jobs:
|
||||
|
||||
+5
-4
@@ -5,13 +5,12 @@ Credits
|
||||
`Leandro Toledo <https://github.com/leandrotoledo>`_.
|
||||
The current development team includes
|
||||
|
||||
- `Hinrich Mahler <https://github.com/Bibo-Joshi>`_ (maintainer)
|
||||
- `Poolitzer <https://github.com/Poolitzer>`_ (community liaison)
|
||||
- `Harshil <https://github.com/harshil21>`_
|
||||
- `Poolitzer <https://github.com/Poolitzer>`_ (maintainer)
|
||||
- `Harshil <https://github.com/harshil21>`_ (maintainer)
|
||||
- `Abdelrahman <https://github.com/aelkheir>`_
|
||||
|
||||
Emeritus maintainers include
|
||||
`Jannes Höke <https://github.com/jh0ker>`_ (`@jh0ker <https://t.me/jh0ker>`_ on Telegram),
|
||||
`Hinrich Mahler <https://github.com/Bibo-Joshi>`_, `Jannes Höke <https://github.com/jh0ker>`_ (`@jh0ker <https://t.me/jh0ker>`_ on Telegram),
|
||||
`Noam Meltzer <https://github.com/tsnoam>`_, `Pieter Schutz <https://github.com/eldinnie>`_ and `Jasmin Bom <https://github.com/jsmnbom>`_.
|
||||
|
||||
Contributors
|
||||
@@ -58,6 +57,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `gamgi <https://github.com/gamgi>`_
|
||||
- `Gauthamram Ravichandran <https://github.com/GauthamramRavichandran>`_
|
||||
- `Harshil <https://github.com/harshil21>`_
|
||||
- `Henok Tesfamikael <https://github.com/hethon>`
|
||||
- `Henry Galue <https://github.com/henryg311>`
|
||||
- `Hugo Damer <https://github.com/HakimusGIT>`_
|
||||
- `ihoru <https://github.com/ihoru>`_
|
||||
@@ -104,6 +104,7 @@ The following wonderful people contributed directly or indirectly to this projec
|
||||
- `Oleg Shlyazhko <https://github.com/ollmer>`_
|
||||
- `Oleg Sushchenko <https://github.com/feuillemorte>`_
|
||||
- `Or Bin <https://github.com/OrBin>`_
|
||||
- `OuYoung <https://github.com/ouyooung>`_
|
||||
- `overquota <https://github.com/overquota>`_
|
||||
- `Pablo Martinez <https://github.com/elpekenin>`_
|
||||
- `Paradox <https://github.com/paradox70>`_
|
||||
|
||||
+2
-6
@@ -11,7 +11,7 @@
|
||||
:target: https://pypi.org/project/python-telegram-bot/
|
||||
:alt: Supported Python versions
|
||||
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-9.3-blue?logo=telegram
|
||||
.. image:: https://img.shields.io/badge/Bot%20API-9.5-blue?logo=telegram
|
||||
:target: https://core.telegram.org/bots/api-changelog
|
||||
:alt: Supported Bot API version
|
||||
|
||||
@@ -43,10 +43,6 @@
|
||||
:target: https://app.codacy.com/gh/python-telegram-bot/python-telegram-bot/dashboard
|
||||
:alt: Code quality: Codacy
|
||||
|
||||
.. image:: https://results.pre-commit.ci/badge/github/python-telegram-bot/python-telegram-bot/master.svg
|
||||
:target: https://results.pre-commit.ci/latest/github/python-telegram-bot/python-telegram-bot/master
|
||||
:alt: pre-commit.ci status
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
:alt: Code Style: Black
|
||||
@@ -81,7 +77,7 @@ After installing_ the library, be sure to check out the section on `working with
|
||||
Telegram API support
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
All types and methods of the Telegram Bot API **9.3** are natively supported by this library.
|
||||
All types and methods of the Telegram Bot API **9.5** are natively supported by this library.
|
||||
In addition, Bot API functionality not yet natively included can still be used as described `in our wiki <https://github.com/python-telegram-bot/python-telegram-bot/wiki/Bot-API-Forward-Compatibility>`_.
|
||||
|
||||
Notable Features
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.14.14"
|
||||
[[pull_requests]]
|
||||
uid = "5110"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update codecov/codecov-action action to v5.5.2"
|
||||
[[pull_requests]]
|
||||
uid = "5112"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update dependency astral-sh/uv to v0.9.28"
|
||||
[[pull_requests]]
|
||||
uid = "5113"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update dependency pytest to v9.0.2"
|
||||
[[pull_requests]]
|
||||
uid = "5114"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update actions/setup-python action to v6.2.0"
|
||||
[[pull_requests]]
|
||||
uid = "5115"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update astral-sh/setup-uv action to v7.2.1"
|
||||
[[pull_requests]]
|
||||
uid = "5116"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update pre-commit hook cachetools to v7"
|
||||
[[pull_requests]]
|
||||
uid = "5117"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
highlights = "Hand over Maintainer Role from `Bibo-Joshi <https://github.com/Bibo-Joshi>`_ to `Poolitzer <https://github.com/Poolitzer>`_ and `Harshil <https://github.com/harshil21>`_"
|
||||
|
||||
[[pull_requests]]
|
||||
uid = "5119"
|
||||
author_uids = ["Bibo-Joshi"]
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.15.0"
|
||||
[[pull_requests]]
|
||||
uid = "5122"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.15.0"
|
||||
[[pull_requests]]
|
||||
uid = "5122"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
other = "Fixing failing sphinx builds"
|
||||
[[pull_requests]]
|
||||
uid = "5124"
|
||||
author_uids = ["Poolitzer"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies = "Update dependency cryptography to v46.0.5 [SECURITY]"
|
||||
[[pull_requests]]
|
||||
uid = "5125"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.15.1"
|
||||
[[pull_requests]]
|
||||
uid = "5135"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,9 @@
|
||||
features = "Full Support for Bot API 9.4"
|
||||
|
||||
pull_requests = [
|
||||
{ uid = "5137", author_uid = "harshil21" },
|
||||
{ uid = "5133", author_uid = "ouyooung"},
|
||||
{ uid = "5141", author_uid = "hethon" },
|
||||
{ uid = "5129", author_uid = "Poolitzer" },
|
||||
{ uid = "5148", author_uid = "harshil21" }
|
||||
]
|
||||
@@ -0,0 +1,5 @@
|
||||
other = "Fix: Moved inline test file to _inline folder"
|
||||
[[pull_requests]]
|
||||
uid = "5140"
|
||||
author_uids = ["Poolitzer"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Replace `pre-commit` with `prek`"
|
||||
[[pull_requests]]
|
||||
uid = "5142"
|
||||
author_uids = ["harshil21"]
|
||||
closes_threads = ["5138"]
|
||||
@@ -0,0 +1,10 @@
|
||||
breaking = """Remove Functionality Deprecated in Bot API 9.3
|
||||
|
||||
* Remove deprecated argument and attribute ``UniqueGiftInfo.last_resale_star_count``.
|
||||
* Remove deprecated argument and attribute ``Bot.get_business_account_gifts.exclude_limited``.
|
||||
* :attr:`telegram.UniqueGift.gift_id` is now a positional argument.
|
||||
"""
|
||||
[[pull_requests]]
|
||||
uid = "5143"
|
||||
author_uids = ["harshil21"]
|
||||
closes_threads = ["5093"]
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Pylint to v4.0.5"
|
||||
[[pull_requests]]
|
||||
uid = "5145"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.15.2"
|
||||
[[pull_requests]]
|
||||
uid = "5146"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.15.4"
|
||||
[[pull_requests]]
|
||||
uid = "5150"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update actions/stale action to v10.2.0"
|
||||
[[pull_requests]]
|
||||
uid = "5151"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update astral-sh/setup-uv action to v7.3.1"
|
||||
[[pull_requests]]
|
||||
uid = "5152"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update codecov/test-results-action action to v1.2.1"
|
||||
[[pull_requests]]
|
||||
uid = "5153"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update dependency astral-sh/uv to v0.10.7"
|
||||
[[pull_requests]]
|
||||
uid = "5154"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
features = "Full support for Bot API 9.5"
|
||||
[[pull_requests]]
|
||||
uid = "5155"
|
||||
author_uids = ["Poolitzer"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.15.5"
|
||||
[[pull_requests]]
|
||||
uid = "5156"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Make CI run after chango commits"
|
||||
[[pull_requests]]
|
||||
uid = "5157"
|
||||
author_uids = ["harshil21"]
|
||||
closes_threads = ["5144"]
|
||||
@@ -0,0 +1,5 @@
|
||||
bugfixes = "Preserve `InlineKeyboardButton` Arguments During `callback_data` Replacement"
|
||||
[[pull_requests]]
|
||||
uid = "5159"
|
||||
author_uids = ["harshil21"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Refactor `TestStickerSetWithRequest` tests"
|
||||
[[pull_requests]]
|
||||
uid = "5161"
|
||||
author_uids = ["harshil21"]
|
||||
closes_threads = ["4514"]
|
||||
@@ -0,0 +1,5 @@
|
||||
dependencies = "Update dependency tornado to v6.5.5 [SECURITY]"
|
||||
[[pull_requests]]
|
||||
uid = "5164"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update Ruff to v0.15.6"
|
||||
[[pull_requests]]
|
||||
uid = "5166"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update dependency sphinx to v9"
|
||||
[[pull_requests]]
|
||||
uid = "5167"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Bump Sphinx Related Dependencies, Fix `uv` and Docs Build"
|
||||
[[pull_requests]]
|
||||
uid = "5168"
|
||||
author_uids = ["harshil21"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update dependency astral-sh/uv to v0.10.10"
|
||||
[[pull_requests]]
|
||||
uid = "5169"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
other = "Update astral-sh/setup-uv action to v7.5.0"
|
||||
[[pull_requests]]
|
||||
uid = "5170"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update GitHub Artifact Actions (major)"
|
||||
[[pull_requests]]
|
||||
uid = "5171"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update sigstore/gh-action-sigstore-python action to v3.2.0"
|
||||
[[pull_requests]]
|
||||
uid = "5172"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update stefanzweifel/git-auto-commit-action action to v7.1.0"
|
||||
[[pull_requests]]
|
||||
uid = "5173"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Update actions/checkout action to v6"
|
||||
[[pull_requests]]
|
||||
uid = "5174"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
internal = "Lock file maintenance"
|
||||
[[pull_requests]]
|
||||
uid = "5175"
|
||||
author_uids = ["renovate[bot]"]
|
||||
closes_threads = []
|
||||
@@ -0,0 +1,5 @@
|
||||
other = "Bump Version to v22.7"
|
||||
[[pull_requests]]
|
||||
uid = "5176"
|
||||
author_uids = ["harshil21"]
|
||||
closes_threads = []
|
||||
@@ -101,7 +101,7 @@ def autodoc_process_docstring(
|
||||
"""
|
||||
|
||||
# 1) Insert the Keyword Args and "Shortcuts" admonitions for the Bot methods
|
||||
method_name = name.split(".")[-1]
|
||||
method_name = name.rsplit(".", maxsplit=1)[0]
|
||||
if (
|
||||
name.startswith("telegram.Bot.")
|
||||
and what == "method"
|
||||
|
||||
@@ -77,7 +77,7 @@ class TGConstXRefRole(PyXRefRole):
|
||||
if (
|
||||
isinstance(value, dtm.datetime)
|
||||
and value == telegram.constants.ZERO_DATE
|
||||
and target in ("telegram.constants.ZERO_DATE",)
|
||||
and target == "telegram.constants.ZERO_DATE"
|
||||
):
|
||||
return repr(value), target
|
||||
sphinx_logger.warning(
|
||||
|
||||
@@ -126,6 +126,10 @@ linkcheck_ignore = [
|
||||
re.escape("https://docs.python-telegram-bot.org/en/doc-fixes"),
|
||||
# Apparently has some human-verification check and gives 403 in the sphinx build
|
||||
re.escape("https://stackoverflow.com/questions/tagged/python-telegram-bot"),
|
||||
# Dead Github accounts:
|
||||
re.escape("https://github.com/SmartDever02"),
|
||||
re.escape("https://github.com/renovate[bot]"),
|
||||
re.escape("https://github.com/roast-lord"),
|
||||
]
|
||||
linkcheck_allowed_redirects = {
|
||||
# Redirects to the default version are okay
|
||||
|
||||
@@ -167,6 +167,8 @@
|
||||
- Used for unpinning a message
|
||||
* - :meth:`~telegram.Bot.unpin_all_chat_messages`
|
||||
- Used for unpinning all pinned chat messages
|
||||
* - :meth:`~telegram.Bot.get_user_profile_audios`
|
||||
- Used for obtaining user's profile audios
|
||||
* - :meth:`~telegram.Bot.get_user_profile_photos`
|
||||
- Used for obtaining user's profile pictures
|
||||
* - :meth:`~telegram.Bot.get_chat`
|
||||
@@ -181,6 +183,8 @@
|
||||
- Used for getting the list of boosts added to a chat
|
||||
* - :meth:`~telegram.Bot.leave_chat`
|
||||
- Used for leaving a chat
|
||||
* - :meth:`~telegram.Bot.set_chat_member_tag`
|
||||
- Used for setting the tag of a chat member
|
||||
|
||||
.. raw:: html
|
||||
|
||||
@@ -245,6 +249,11 @@
|
||||
- Used for setting the name of the bot
|
||||
* - :meth:`~telegram.Bot.get_my_name`
|
||||
- Used for obtaining the name of the bot
|
||||
* - :meth:`~telegram.Bot.set_my_profile_photo`
|
||||
- Used for setting the profile photo of the bot
|
||||
* - :meth:`~telegram.Bot.remove_my_profile_photo`
|
||||
- Used for removing the profile photo of the bot
|
||||
|
||||
|
||||
.. raw:: html
|
||||
|
||||
|
||||
@@ -65,6 +65,8 @@ Available Types
|
||||
telegram.chatmemberowner
|
||||
telegram.chatmemberrestricted
|
||||
telegram.chatmemberupdated
|
||||
telegram.chatownerchanged
|
||||
telegram.chatownerleft
|
||||
telegram.chatpermissions
|
||||
telegram.chatphoto
|
||||
telegram.chatshared
|
||||
@@ -191,6 +193,7 @@ Available Types
|
||||
telegram.update
|
||||
telegram.user
|
||||
telegram.userchatboosts
|
||||
telegram.userprofileaudios
|
||||
telegram.userprofilephotos
|
||||
telegram.userrating
|
||||
telegram.usersshared
|
||||
@@ -201,6 +204,7 @@ Available Types
|
||||
telegram.videochatscheduled
|
||||
telegram.videochatstarted
|
||||
telegram.videonote
|
||||
telegram.videoquality
|
||||
telegram.voice
|
||||
telegram.webappdata
|
||||
telegram.webappinfo
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
ChatOwnerChanged
|
||||
================
|
||||
|
||||
.. autoclass:: telegram.ChatOwnerChanged
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
ChatOwnerLeft
|
||||
=============
|
||||
|
||||
.. autoclass:: telegram.ChatOwnerLeft
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,6 @@
|
||||
UserProfileAudios
|
||||
=================
|
||||
|
||||
.. autoclass:: telegram.UserProfileAudios
|
||||
:members:
|
||||
:show-inheritance:
|
||||
@@ -0,0 +1,8 @@
|
||||
VideoQuality
|
||||
============
|
||||
.. Also lists methods of _BaseMedium, but not the ones of TelegramObject
|
||||
|
||||
.. autoclass:: telegram.VideoQuality
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members: TelegramObject, object
|
||||
+11
-11
@@ -65,7 +65,7 @@ all = [
|
||||
]
|
||||
callback-data = [
|
||||
# Cachetools doesn't have a strict stability policy. Let's be cautious for now.
|
||||
"cachetools>=5.3.3,<6.3.0",
|
||||
"cachetools>=7.0.0,<8.0.0",
|
||||
]
|
||||
ext = [
|
||||
"python-telegram-bot[callback-data,job-queue,rate-limiter,webhooks]",
|
||||
@@ -98,7 +98,7 @@ tests = [
|
||||
# required for building the wheels for releases
|
||||
"build",
|
||||
# For the test suite
|
||||
"pytest==9.0.1",
|
||||
"pytest==9.0.2",
|
||||
# needed because pytest doesn't come with native support for coroutines as tests
|
||||
"pytest-asyncio==0.21.2",
|
||||
# xdist runs tests in parallel
|
||||
@@ -117,12 +117,12 @@ tests = [
|
||||
]
|
||||
docs = [
|
||||
"chango~=0.6.0; python_version >= '3.12'",
|
||||
"sphinx==8.2.3; python_version >= '3.11'",
|
||||
"furo==2025.9.25",
|
||||
"sphinx==9.1.0; python_version >= '3.12'",
|
||||
"furo==2025.12.19",
|
||||
"sphinx-paramlinks==0.6.0",
|
||||
"sphinxcontrib-mermaid==1.0.0",
|
||||
"sphinxcontrib-mermaid==2.0.1",
|
||||
"sphinx-copybutton==0.5.2",
|
||||
"sphinx-inline-tabs==2023.4.21",
|
||||
"sphinx-inline-tabs==2025.12.21.14",
|
||||
# Temporary. See #4387
|
||||
"sphinx-build-compatibility @ git+https://github.com/readthedocs/sphinx-build-compatibility.git@58aabc5f207c6c2421f23d3578adc0b14af57047",
|
||||
# For python 3.14 support, we need a version of pydantic-core >= 2.35.0, since it upgrades the
|
||||
@@ -132,10 +132,10 @@ docs = [
|
||||
"pydantic >= 2.12.0a1 ; python_version >= '3.14'"
|
||||
]
|
||||
linting = [
|
||||
"pre-commit",
|
||||
"ruff==0.14.13",
|
||||
"mypy==1.18.2",
|
||||
"pylint==4.0.4"
|
||||
"prek",
|
||||
"ruff==0.15.6",
|
||||
"mypy==1.19.1",
|
||||
"pylint==4.0.5"
|
||||
]
|
||||
all = [{ include-group = "tests" }, { include-group = "docs" }, { include-group = "linting"}]
|
||||
|
||||
@@ -165,7 +165,7 @@ show-fixes = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
typing-extensions = false
|
||||
ignore = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PERF203"]
|
||||
ignore = ["PLR2004", "PLR0911", "PLR0912", "PLR0913", "PLR0915", "PERF203", "ASYNC240"]
|
||||
select = ["E", "F", "I", "PL", "UP", "RUF", "PTH", "C4", "B", "PIE", "SIM", "RET", "RSE",
|
||||
"G", "ISC", "PT", "ASYNC", "TCH", "SLOT", "PERF", "PYI", "FLY", "AIR", "RUF022",
|
||||
"RUF023", "Q", "INP", "W", "YTT", "DTZ", "ARG", "T20", "FURB", "DOC", "TRY",
|
||||
|
||||
@@ -79,6 +79,8 @@ __all__ = (
|
||||
"ChatMemberOwner",
|
||||
"ChatMemberRestricted",
|
||||
"ChatMemberUpdated",
|
||||
"ChatOwnerChanged",
|
||||
"ChatOwnerLeft",
|
||||
"ChatPermissions",
|
||||
"ChatPhoto",
|
||||
"ChatShared",
|
||||
@@ -295,6 +297,7 @@ __all__ = (
|
||||
"Update",
|
||||
"User",
|
||||
"UserChatBoosts",
|
||||
"UserProfileAudios",
|
||||
"UserProfilePhotos",
|
||||
"UserRating",
|
||||
"UsersShared",
|
||||
@@ -305,6 +308,7 @@ __all__ = (
|
||||
"VideoChatScheduled",
|
||||
"VideoChatStarted",
|
||||
"VideoNote",
|
||||
"VideoQuality",
|
||||
"Voice",
|
||||
"WebAppData",
|
||||
"WebAppInfo",
|
||||
@@ -400,6 +404,7 @@ from ._chatmember import (
|
||||
ChatMemberRestricted,
|
||||
)
|
||||
from ._chatmemberupdated import ChatMemberUpdated
|
||||
from ._chatowner import ChatOwnerChanged, ChatOwnerLeft
|
||||
from ._chatpermissions import ChatPermissions
|
||||
from ._checklists import Checklist, ChecklistTask, ChecklistTasksAdded, ChecklistTasksDone
|
||||
from ._choseninlineresult import ChosenInlineResult
|
||||
@@ -442,6 +447,7 @@ from ._files.sticker import MaskPosition, Sticker, StickerSet
|
||||
from ._files.venue import Venue
|
||||
from ._files.video import Video
|
||||
from ._files.videonote import VideoNote
|
||||
from ._files.videoquality import VideoQuality
|
||||
from ._files.voice import Voice
|
||||
from ._forcereply import ForceReply
|
||||
from ._forumtopic import (
|
||||
@@ -607,6 +613,7 @@ from ._uniquegift import (
|
||||
)
|
||||
from ._update import Update
|
||||
from ._user import User
|
||||
from ._userprofileaudios import UserProfileAudios
|
||||
from ._userprofilephotos import UserProfilePhotos
|
||||
from ._userrating import UserRating
|
||||
from ._videochat import (
|
||||
|
||||
+254
-81
@@ -35,6 +35,8 @@ from typing import (
|
||||
no_type_check,
|
||||
)
|
||||
|
||||
from telegram._userprofileaudios import UserProfileAudios
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography.hazmat.primitives import serialization
|
||||
@@ -45,7 +47,7 @@ except ImportError:
|
||||
serialization = None # type: ignore[assignment]
|
||||
CRYPTO_INSTALLED = False
|
||||
|
||||
from telegram._botcommand import BotCommand
|
||||
from telegram._botcommand import BotCommand # pylint: disable=ungrouped-imports
|
||||
from telegram._botcommandscope import BotCommandScope
|
||||
from telegram._botdescription import BotDescription, BotShortDescription
|
||||
from telegram._botname import BotName
|
||||
@@ -106,14 +108,13 @@ from telegram._utils.types import (
|
||||
TimePeriod,
|
||||
)
|
||||
from telegram._utils.warnings import warn
|
||||
from telegram._utils.warnings_transition import build_deprecation_warning_message
|
||||
from telegram._webhookinfo import WebhookInfo
|
||||
from telegram.constants import InlineQueryLimit, ReactionEmoji
|
||||
from telegram.error import EndPointNotFound, InvalidToken
|
||||
from telegram.request import BaseRequest, RequestData
|
||||
from telegram.request._httpxrequest import HTTPXRequest
|
||||
from telegram.request._requestparameter import RequestParameter
|
||||
from telegram.warnings import PTBDeprecationWarning, PTBUserWarning
|
||||
from telegram.warnings import PTBUserWarning
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import (
|
||||
@@ -1217,10 +1218,14 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> bool:
|
||||
"""Use this method to stream a partial message to a user while the message is being
|
||||
generated; supported only for bots with forum topic mode enabled.
|
||||
generated.
|
||||
|
||||
.. versionadded:: 22.6
|
||||
|
||||
.. versionchanged:: 22.7
|
||||
Now all bots can use this method.
|
||||
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int`): Unique identifier for the target private chat.
|
||||
draft_id (:obj:`int`): Unique identifier of the message draft; must be non-zero.
|
||||
@@ -5931,6 +5936,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
|
||||
can_edit_stories: bool | None = None,
|
||||
can_delete_stories: bool | None = None,
|
||||
can_manage_direct_messages: bool | None = None,
|
||||
can_manage_tags: bool | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
@@ -5958,7 +5964,6 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
|
||||
administrator privilege.
|
||||
|
||||
.. versionadded:: 13.4
|
||||
|
||||
can_manage_video_chats (:obj:`bool`, optional): Pass :obj:`True`, if the administrator
|
||||
can manage video chats.
|
||||
|
||||
@@ -5994,7 +5999,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
|
||||
.. versionadded:: 20.6
|
||||
can_edit_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
|
||||
edit stories posted by other users, post stories to the chat page, pin chat
|
||||
stories, and access the chat's story archive
|
||||
stories, and access the chat's story archive.
|
||||
|
||||
.. versionadded:: 20.6
|
||||
can_delete_stories (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
|
||||
@@ -6003,9 +6008,13 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
|
||||
.. versionadded:: 20.6
|
||||
can_manage_direct_messages (:obj:`bool`, optional): Pass :obj:`True`, if the
|
||||
administrator can manage direct messages within the channel and decline suggested
|
||||
posts; for channels only
|
||||
posts; for channels only.
|
||||
|
||||
.. versionadded:: 22.4
|
||||
can_manage_tags (:obj:`bool`, optional): Pass :obj:`True`, if the administrator can
|
||||
edit the tags of regular members; for groups and supergroups only.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
@@ -6033,6 +6042,7 @@ class Bot(TelegramObject, contextlib.AbstractAsyncContextManager["Bot"]):
|
||||
"can_edit_stories": can_edit_stories,
|
||||
"can_delete_stories": can_delete_stories,
|
||||
"can_manage_direct_messages": can_manage_direct_messages,
|
||||
"can_manage_tags": can_manage_tags,
|
||||
}
|
||||
|
||||
return await self._post(
|
||||
@@ -8922,8 +8932,8 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> ForumTopic:
|
||||
"""
|
||||
Use this method to create a topic in a forum supergroup chat. The bot must be
|
||||
an administrator in the chat for this to work and must have
|
||||
Use this method to create a topic in a forum supergroup chat or a private chat with a user.
|
||||
The bot must be an administrator in the chat for this to work and must have
|
||||
:paramref:`~telegram.ChatAdministratorRights.can_manage_topics` administrator rights.
|
||||
|
||||
.. versionadded:: 20.0
|
||||
@@ -9942,8 +9952,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
||||
exclude_unsaved: bool | None = None,
|
||||
exclude_saved: bool | None = None,
|
||||
exclude_unlimited: bool | None = None,
|
||||
# tags: deprecated 22.6; bot api 9.3
|
||||
exclude_limited: bool | None = None,
|
||||
exclude_unique: bool | None = None,
|
||||
sort_by_price: bool | None = None,
|
||||
offset: str | None = None,
|
||||
@@ -9964,6 +9972,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
||||
|
||||
.. versionadded:: 22.1
|
||||
|
||||
.. versionremoved:: 22.7
|
||||
Bot API 9.3 removed the :paramref:`exclude_limited` parameter. Use
|
||||
:paramref:`exclude_limited_upgradable` and :paramref:`exclude_limited_non_upgradable`
|
||||
instead.
|
||||
|
||||
Args:
|
||||
business_connection_id (:obj:`str`): Unique identifier of the business connection.
|
||||
exclude_unsaved (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that aren't
|
||||
@@ -9972,13 +9985,6 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
||||
to the account's profile page.
|
||||
exclude_unlimited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can
|
||||
be purchased an unlimited number of times.
|
||||
exclude_limited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can be
|
||||
purchased a limited number of times.
|
||||
|
||||
.. deprecated:: 22.6
|
||||
Bot API 9.3 deprecated this parameter in favor of
|
||||
:paramref:`exclude_limited_upgradabale` and
|
||||
:paramref:`exclude_limited_non_upgradable`.
|
||||
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
|
||||
that can be purchased a limited number of times and can be upgraded to unique.
|
||||
|
||||
@@ -10009,25 +10015,11 @@ CUSTOM_EMOJI_IDENTIFIER_LIMIT` custom emoji identifiers can be specified.
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
if exclude_limited is not None:
|
||||
self._warn(
|
||||
PTBDeprecationWarning(
|
||||
version="22.6",
|
||||
message=build_deprecation_warning_message(
|
||||
deprecated_name="exclude_limited",
|
||||
new_name="exclude_limited_(non_)upgradable",
|
||||
bot_api_version="9.3",
|
||||
object_type="parameter",
|
||||
),
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
data: JSONDict = {
|
||||
"business_connection_id": business_connection_id,
|
||||
"exclude_unsaved": exclude_unsaved,
|
||||
"exclude_saved": exclude_saved,
|
||||
"exclude_unlimited": exclude_unlimited,
|
||||
"exclude_limited": exclude_limited,
|
||||
"exclude_limited_upgradable": exclude_limited_upgradable,
|
||||
"exclude_limited_non_upgradable": exclude_limited_non_upgradable,
|
||||
"exclude_unique": exclude_unique,
|
||||
@@ -11714,7 +11706,7 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
|
||||
Reposts a story on behalf of a business account from another business account.
|
||||
Both business accounts must be managed by the same bot, and the story on the source account
|
||||
must have been posted (or reposted) by the bot. Requires the
|
||||
:attr:`~telegram.BusinessBotRight.can_manage_stories` business bot right for both
|
||||
:attr:`~telegram.BusinessBotRights.can_manage_stories` business bot right for both
|
||||
business accounts.
|
||||
|
||||
.. versionadded:: 22.6
|
||||
@@ -11726,10 +11718,10 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
|
||||
from_story_id (:obj:`int`): Unique identifier of the story that should be reposted
|
||||
active_period (:obj:`int` | :class:`datetime.timedelta`): Period after which the story
|
||||
is moved to the archive, in seconds; must be one of
|
||||
:tg-const:`telegram.constants.StoryLimit.SIX_HOURS`,
|
||||
:tg-const:`telegram.constants.StoryLimit.TWELVE_HOURS`,
|
||||
:tg-const:`telegram.constants.StoryLimit.ONE_DAY`, or
|
||||
:tg-const:`telegram.constants.StoryLimit.TWO_DAYS`.
|
||||
:tg-const:`telegram.constants.StoryLimit.ACTIVITY_SIX_HOURS`,
|
||||
:tg-const:`telegram.constants.StoryLimit.ACTIVITY_TWELVE_HOURS`,
|
||||
:tg-const:`telegram.constants.StoryLimit.ACTIVITY_ONE_DAY`, or
|
||||
:tg-const:`telegram.constants.StoryLimit.ACTIVITY_TWO_DAYS`.
|
||||
post_to_chat_page (:obj:`bool`, optional): Pass :obj:`True` to keep the story
|
||||
accessible after it expires.
|
||||
protect_content (:obj:`bool`, optional): Pass :obj:`True` if the content of the story
|
||||
@@ -11784,24 +11776,27 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
|
||||
|
||||
.. versionadded:: 22.6
|
||||
|
||||
user_id (:obj:`int`): Unique identifier of the user
|
||||
exclude_unlimited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can be
|
||||
purchased an unlimited number of times
|
||||
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
|
||||
can be purchased a limited number of times and can be upgraded to unique
|
||||
exclude_limited_non_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
|
||||
that can be purchased a limited number of times and can't be upgraded to unique
|
||||
exclude_from_blockchain (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
|
||||
were assigned from the TON blockchain and can't be resold or transferred in Telegram
|
||||
exclude_unique (:obj:`bool`, optional): Pass :obj:`True` to exclude unique gifts
|
||||
sort_by_price (:obj:`bool`, optional): Pass :obj:`True` to sort results by gift price
|
||||
instead of send date. Sorting is applied before pagination.
|
||||
offset (:obj:`str`, optional): Offset of the first entry to return as received from the
|
||||
previous request; use an empty string to get the first chunk of results
|
||||
limit (:obj:`int`, optional): The maximum number of gifts to be returned;
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MIN_GIFT_RESULTS` -
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`.
|
||||
Defaults to :tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`
|
||||
Args:
|
||||
user_id (:obj:`int`): Unique identifier of the user
|
||||
exclude_unlimited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can
|
||||
be purchased an unlimited number of times
|
||||
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
|
||||
that can be purchased a limited number of times and can be upgraded to unique
|
||||
exclude_limited_non_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude
|
||||
gifts that can be purchased a limited number of times and can't be upgraded to
|
||||
unique.
|
||||
exclude_from_blockchain (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
|
||||
were assigned from the TON blockchain and can't be resold or transferred in
|
||||
Telegram.
|
||||
exclude_unique (:obj:`bool`, optional): Pass :obj:`True` to exclude unique gifts
|
||||
sort_by_price (:obj:`bool`, optional): Pass :obj:`True` to sort results by gift price
|
||||
instead of send date. Sorting is applied before pagination.
|
||||
offset (:obj:`str`, optional): Offset of the first entry to return as received from the
|
||||
previous request; use an empty string to get the first chunk of results
|
||||
limit (:obj:`int`, optional): The maximum number of gifts to be returned;
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MIN_GIFT_RESULTS` -
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESULTS`.
|
||||
Defaults to :tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESULTS`
|
||||
|
||||
Returns:
|
||||
:class:`telegram.OwnedGifts`: The owned gifts for the user.
|
||||
@@ -11857,32 +11852,34 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
|
||||
.. versionadded:: 22.6
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
exclude_unsaved (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that aren't
|
||||
saved to the chat's profile page. Always :obj:`True`, unless the bot has the
|
||||
:attr:`~telegram.ChatAdministratorRights..can_post_messages` administrator right in the
|
||||
channel.
|
||||
exclude_saved (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that are saved to
|
||||
the chat's profile page. Always :obj:`False`, unless the bot has the
|
||||
:attr:`~telegram.ChatAdministratorRights..can_post_messages` administrator right in the
|
||||
channel.
|
||||
exclude_unlimited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can be
|
||||
purchased an unlimited number of times
|
||||
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
|
||||
can be purchased a limited number of times and can be upgraded to unique
|
||||
exclude_limited_non_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
|
||||
that can be purchased a limited number of times and can't be upgraded to unique
|
||||
exclude_from_blockchain (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
|
||||
were assigned from the TON blockchain and can't be resold or transferred in Telegram
|
||||
exclude_unique (:obj:`bool`, optional): Pass :obj:`True` to exclude unique gifts
|
||||
sort_by_price (:obj:`bool`, optional): Pass :obj:`True` to sort results by gift price
|
||||
instead of send date. Sorting is applied before pagination.
|
||||
offset (:obj:`str`, optional): Offset of the first entry to return as received from the
|
||||
previous request; use an empty string to get the first chunk of results
|
||||
limit (:obj:`int`, optional): The maximum number of gifts to be returned;
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MIN_GIFT_RESULTS` -
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`.
|
||||
Defaults to :tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESLUTS`
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_channel|
|
||||
exclude_unsaved (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that aren't
|
||||
saved to the chat's profile page. Always :obj:`True`, unless the bot has the
|
||||
:attr:`~telegram.ChatAdministratorRights.can_post_messages` administrator right in
|
||||
the channel.
|
||||
exclude_saved (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that are saved
|
||||
to the chat's profile page. Always :obj:`False`, unless the bot has the
|
||||
:attr:`~telegram.ChatAdministratorRights.can_post_messages` administrator right in
|
||||
the channel.
|
||||
exclude_unlimited (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that can
|
||||
be purchased an unlimited number of times
|
||||
exclude_limited_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts
|
||||
that can be purchased a limited number of times and can be upgraded to unique
|
||||
exclude_limited_non_upgradable (:obj:`bool`, optional): Pass :obj:`True` to exclude
|
||||
gifts that can be purchased a limited number of times and can't be upgraded to
|
||||
unique.
|
||||
exclude_from_blockchain (:obj:`bool`, optional): Pass :obj:`True` to exclude gifts that
|
||||
were assigned from the TON blockchain and can't be resold or transferred in
|
||||
Telegram.
|
||||
exclude_unique (:obj:`bool`, optional): Pass :obj:`True` to exclude unique gifts
|
||||
sort_by_price (:obj:`bool`, optional): Pass :obj:`True` to sort results by gift price
|
||||
instead of send date. Sorting is applied before pagination.
|
||||
offset (:obj:`str`, optional): Offset of the first entry to return as received from the
|
||||
previous request; use an empty string to get the first chunk of results
|
||||
limit (:obj:`int`, optional): The maximum number of gifts to be returned;
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MIN_GIFT_RESULTS` -
|
||||
:tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESULTS`.
|
||||
Defaults to :tg-const:`~telegram.constants.BusinessLimit.MAX_GIFT_RESULTS`
|
||||
|
||||
Returns:
|
||||
:class:`telegram.OwnedGifts`: The owned gifts for the chat.
|
||||
@@ -11916,6 +11913,174 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
|
||||
)
|
||||
return OwnedGifts.de_json(result, self)
|
||||
|
||||
async def set_my_profile_photo(
|
||||
self,
|
||||
photo: "InputProfilePhoto",
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Changes the profile photo of the bot.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Args:
|
||||
photo (:class:`telegram.InputProfilePhoto`): The new profile photo to set.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
data: JSONDict = {
|
||||
"photo": photo,
|
||||
}
|
||||
return await self._post(
|
||||
"setMyProfilePhoto",
|
||||
data,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
async def remove_my_profile_photo(
|
||||
self,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Removes the profile photo of the bot. Requires no parameters.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
|
||||
"""
|
||||
|
||||
return await self._post(
|
||||
"removeMyProfilePhoto",
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
async def get_user_profile_audios(
|
||||
self,
|
||||
user_id: int,
|
||||
offset: int | None = None,
|
||||
limit: int | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> UserProfileAudios:
|
||||
"""
|
||||
Use this method to get a list of profile audios for a user.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Args:
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
offset (:obj:`int`, optional): Sequential number of the first audio to be returned.
|
||||
By default, all audios are returned.
|
||||
limit (:obj:`int`, optional): Limits the number of audios to be retrieved. Values
|
||||
between :tg-const:`telegram.constants.UserProfileAudiosLimit.MIN_LIMIT`-
|
||||
:tg-const:`telegram.constants.UserProfileAudiosLimit.MAX_LIMIT` are accepted.
|
||||
Defaults to :tg-const:`telegram.constants.UserProfileAudiosLimit.MAX_LIMIT`.
|
||||
|
||||
Returns:
|
||||
:class:`telegram.UserProfileAudios`
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
|
||||
data = {
|
||||
"user_id": user_id,
|
||||
"offset": offset,
|
||||
"limit": limit,
|
||||
}
|
||||
|
||||
result = await self._post(
|
||||
"getUserProfileAudios",
|
||||
data,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
return UserProfileAudios.de_json(result, self)
|
||||
|
||||
async def set_chat_member_tag(
|
||||
self,
|
||||
chat_id: int | str,
|
||||
user_id: int,
|
||||
tag: str | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Use this method to set a tag for a regular member in a group or a supergroup. The bot must
|
||||
be an administrator in the chat for this to work and must have the
|
||||
:attr:`~telegram.ChatMemberAdministrator.can_manage_tags` administrator right.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Args:
|
||||
chat_id (:obj:`int` | :obj:`str`): |chat_id_group|
|
||||
user_id (:obj:`int`): Unique identifier of the target user.
|
||||
tag (:obj:`str`, optional): New tag for the member;
|
||||
0-:tg-const:`telegram.constants.TagLimit.MAX_TAG_LENGTH` characters, emoji are not
|
||||
allowed.
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
|
||||
Raises:
|
||||
:class:`telegram.error.TelegramError`
|
||||
"""
|
||||
data: JSONDict = {
|
||||
"chat_id": chat_id,
|
||||
"user_id": user_id,
|
||||
"tag": tag,
|
||||
}
|
||||
|
||||
return await self._post(
|
||||
"setChatMemberTag",
|
||||
data,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
def to_dict(self, recursive: bool = True) -> JSONDict: # noqa: ARG002
|
||||
"""See :meth:`telegram.TelegramObject.to_dict`."""
|
||||
data: JSONDict = {"id": self.id, "username": self.username, "first_name": self.first_name}
|
||||
@@ -12248,3 +12413,11 @@ CHAT_ACTIVITY_TIMEOUT` seconds.
|
||||
"""Alias for :meth:`get_user_gifts`"""
|
||||
getChatGifts = get_chat_gifts
|
||||
"""Alias for :meth:`get_chat_gifts`"""
|
||||
removeMyProfilePhoto = remove_my_profile_photo
|
||||
"""Alias for :meth:`remove_my_profile_photo`"""
|
||||
setMyProfilePhoto = set_my_profile_photo
|
||||
"""Alias for :meth:`set_my_profile_photo`"""
|
||||
getUserProfileAudios = get_user_profile_audios
|
||||
"""Alias for :meth:`get_user_profile_audios`"""
|
||||
setChatMemberTag = set_chat_member_tag
|
||||
"""Alias for :meth:`set_chat_member_tag`"""
|
||||
|
||||
@@ -619,6 +619,7 @@ class _ChatBase(TelegramObject):
|
||||
can_edit_stories: bool | None = None,
|
||||
can_delete_stories: bool | None = None,
|
||||
can_manage_direct_messages: bool | None = None,
|
||||
can_manage_tags: bool | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
@@ -670,6 +671,7 @@ class _ChatBase(TelegramObject):
|
||||
can_edit_stories=can_edit_stories,
|
||||
can_delete_stories=can_delete_stories,
|
||||
can_manage_direct_messages=can_manage_direct_messages,
|
||||
can_manage_tags=can_manage_tags,
|
||||
)
|
||||
|
||||
async def restrict_member(
|
||||
@@ -3995,6 +3997,41 @@ class _ChatBase(TelegramObject):
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
async def set_chat_member_tag(
|
||||
self,
|
||||
user_id: int,
|
||||
tag: str | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Shortcut for::
|
||||
|
||||
await bot.set_chat_member_tag(chat_id=update.effective_chat.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.set_chat_member_tag`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
"""
|
||||
return await self.get_bot().set_chat_member_tag(
|
||||
chat_id=self.id,
|
||||
user_id=user_id,
|
||||
tag=tag,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
|
||||
class Chat(_ChatBase):
|
||||
"""This object represents a chat.
|
||||
|
||||
@@ -31,7 +31,8 @@ class ChatAdministratorRights(TelegramObject):
|
||||
:attr:`can_promote_members`, :attr:`can_change_info`, :attr:`can_invite_users`,
|
||||
:attr:`can_post_messages`, :attr:`can_edit_messages`, :attr:`can_pin_messages`,
|
||||
:attr:`can_manage_topics`, :attr:`can_post_stories`, :attr:`can_delete_stories`,
|
||||
:attr:`can_edit_stories` and :attr:`can_manage_direct_messages` are equal.
|
||||
:attr:`can_edit_stories`, :attr:`can_manage_direct_messages` and :attr:`can_manage_tags` are
|
||||
equal.
|
||||
|
||||
.. versionadded:: 20.0
|
||||
|
||||
@@ -52,6 +53,10 @@ class ChatAdministratorRights(TelegramObject):
|
||||
:attr:`can_manage_direct_messages` is considered as well when comparing objects of
|
||||
this type in terms of equality.
|
||||
|
||||
.. versionchanged:: 22.7
|
||||
:attr:`can_manage_tags` is considered as well when comparing objects of this type in terms
|
||||
of equality.
|
||||
|
||||
Args:
|
||||
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
|
||||
can_manage_chat (:obj:`bool`): :obj:`True`, if the administrator can access the chat event
|
||||
@@ -104,6 +109,11 @@ class ChatAdministratorRights(TelegramObject):
|
||||
manage direct messages of the channel and decline suggested posts; for channels only.
|
||||
|
||||
.. versionadded:: 22.4
|
||||
can_manage_tags (:obj:`bool`, optional): :obj:`True`, if the administrator can edit the
|
||||
tags of regular members; for groups and supergroups only. If omitted defaults to the
|
||||
value of :attr:`can_pin_messages`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
is_anonymous (:obj:`bool`): :obj:`True`, if the user's presence in the chat is hidden.
|
||||
@@ -157,6 +167,11 @@ class ChatAdministratorRights(TelegramObject):
|
||||
manage direct messages of the channel and decline suggested posts; for channels only.
|
||||
|
||||
.. versionadded:: 22.4
|
||||
can_manage_tags (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit the
|
||||
tags of regular members; for groups and supergroups only. If omitted defaults to the
|
||||
value of :attr:`can_pin_messages`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
@@ -168,6 +183,7 @@ class ChatAdministratorRights(TelegramObject):
|
||||
"can_invite_users",
|
||||
"can_manage_chat",
|
||||
"can_manage_direct_messages",
|
||||
"can_manage_tags",
|
||||
"can_manage_topics",
|
||||
"can_manage_video_chats",
|
||||
"can_pin_messages",
|
||||
@@ -196,6 +212,7 @@ class ChatAdministratorRights(TelegramObject):
|
||||
can_pin_messages: bool | None = None,
|
||||
can_manage_topics: bool | None = None,
|
||||
can_manage_direct_messages: bool | None = None,
|
||||
can_manage_tags: bool | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> None:
|
||||
@@ -218,6 +235,7 @@ class ChatAdministratorRights(TelegramObject):
|
||||
self.can_pin_messages: bool | None = can_pin_messages
|
||||
self.can_manage_topics: bool | None = can_manage_topics
|
||||
self.can_manage_direct_messages: bool | None = can_manage_direct_messages
|
||||
self.can_manage_tags: bool | None = can_manage_tags
|
||||
|
||||
self._id_attrs = (
|
||||
self.is_anonymous,
|
||||
@@ -236,6 +254,7 @@ class ChatAdministratorRights(TelegramObject):
|
||||
self.can_edit_stories,
|
||||
self.can_delete_stories,
|
||||
self.can_manage_direct_messages,
|
||||
self.can_manage_tags,
|
||||
)
|
||||
|
||||
self._freeze()
|
||||
|
||||
@@ -27,6 +27,7 @@ from telegram._birthdate import Birthdate
|
||||
from telegram._chat import Chat, _ChatBase
|
||||
from telegram._chatlocation import ChatLocation
|
||||
from telegram._chatpermissions import ChatPermissions
|
||||
from telegram._files.audio import Audio
|
||||
from telegram._files.chatphoto import ChatPhoto
|
||||
from telegram._gifts import AcceptedGiftTypes
|
||||
from telegram._reaction import ReactionType
|
||||
@@ -247,6 +248,11 @@ class ChatFullInfo(_ChatBase):
|
||||
have to pay to send a message to the chat
|
||||
|
||||
.. versionadded:: 22.6
|
||||
first_profile_audio (:obj:`telegram.Audio`, optional): For private chats, the first audio
|
||||
added to the profile of the user.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
|
||||
Attributes:
|
||||
id (:obj:`int`): Unique identifier for this chat.
|
||||
@@ -432,6 +438,10 @@ class ChatFullInfo(_ChatBase):
|
||||
have to pay to send a message to the chat
|
||||
|
||||
.. versionadded:: 22.6
|
||||
first_profile_audio (:obj:`telegram.Audio`): Optional. For private chats, the first audio
|
||||
added to the profile of the user.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
.. _accent colors: https://core.telegram.org/bots/api#accent-colors
|
||||
.. _topics: https://telegram.org/blog/topics-in-groups-collectible-usernames#topics-in-groups
|
||||
@@ -456,6 +466,7 @@ class ChatFullInfo(_ChatBase):
|
||||
"description",
|
||||
"emoji_status_custom_emoji_id",
|
||||
"emoji_status_expiration_date",
|
||||
"first_profile_audio",
|
||||
"has_aggressive_anti_spam_enabled",
|
||||
"has_hidden_members",
|
||||
"has_private_forwards",
|
||||
@@ -534,6 +545,7 @@ class ChatFullInfo(_ChatBase):
|
||||
rating: UserRating | None = None,
|
||||
unique_gift_colors: UniqueGiftColors | None = None,
|
||||
paid_message_star_count: int | None = None,
|
||||
first_profile_audio: Audio | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -600,6 +612,7 @@ class ChatFullInfo(_ChatBase):
|
||||
self.rating: UserRating | None = rating
|
||||
self.unique_gift_colors: UniqueGiftColors | None = unique_gift_colors
|
||||
self.paid_message_star_count: int | None = paid_message_star_count
|
||||
self.first_profile_audio: Audio | None = first_profile_audio
|
||||
|
||||
@property
|
||||
def slow_mode_delay(self) -> int | dtm.timedelta | None:
|
||||
@@ -656,5 +669,6 @@ class ChatFullInfo(_ChatBase):
|
||||
data["unique_gift_colors"] = de_json_optional(
|
||||
data.get("unique_gift_colors"), UniqueGiftColors, bot
|
||||
)
|
||||
data["first_profile_audio"] = de_json_optional(data.get("first_profile_audio"), Audio, bot)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
||||
@@ -257,6 +257,11 @@ class ChatMemberAdministrator(ChatMember):
|
||||
manage direct messages of the channel and decline suggested posts; for channels only.
|
||||
|
||||
.. versionadded:: 22.4
|
||||
can_manage_tags (:obj:`bool`, optional): :obj:`True`, if the administrator can edit the
|
||||
tags of regular members; for groups and supergroups only. If omitted defaults to the
|
||||
value of :attr:`can_pin_messages`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
@@ -317,10 +322,15 @@ class ChatMemberAdministrator(ChatMember):
|
||||
|
||||
.. versionadded:: 20.0
|
||||
custom_title (:obj:`str`): Optional. Custom title for this user.
|
||||
can_manage_direct_messages (:obj:`bool`, optional): :obj:`True`, if the administrator can
|
||||
can_manage_direct_messages (:obj:`bool`): Optional. :obj:`True`, if the administrator can
|
||||
manage direct messages of the channel and decline suggested posts; for channels only.
|
||||
|
||||
.. versionadded:: 22.4
|
||||
can_manage_tags (:obj:`bool`): Optional. :obj:`True`, if the administrator can edit the
|
||||
tags of regular members; for groups and supergroups only. If omitted defaults to the
|
||||
value of :attr:`can_pin_messages`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
@@ -333,6 +343,7 @@ class ChatMemberAdministrator(ChatMember):
|
||||
"can_invite_users",
|
||||
"can_manage_chat",
|
||||
"can_manage_direct_messages",
|
||||
"can_manage_tags",
|
||||
"can_manage_topics",
|
||||
"can_manage_video_chats",
|
||||
"can_pin_messages",
|
||||
@@ -365,6 +376,7 @@ class ChatMemberAdministrator(ChatMember):
|
||||
can_manage_topics: bool | None = None,
|
||||
custom_title: str | None = None,
|
||||
can_manage_direct_messages: bool | None = None,
|
||||
can_manage_tags: bool | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -389,6 +401,7 @@ class ChatMemberAdministrator(ChatMember):
|
||||
self.can_manage_topics: bool | None = can_manage_topics
|
||||
self.custom_title: str | None = custom_title
|
||||
self.can_manage_direct_messages: bool | None = can_manage_direct_messages
|
||||
self.can_manage_tags: bool | None = can_manage_tags
|
||||
|
||||
|
||||
class ChatMemberMember(ChatMember):
|
||||
@@ -404,6 +417,9 @@ class ChatMemberMember(ChatMember):
|
||||
expire.
|
||||
|
||||
.. versionadded:: 21.5
|
||||
tag (:obj:`str`, optional): Tag of the member.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
@@ -413,21 +429,29 @@ class ChatMemberMember(ChatMember):
|
||||
expire.
|
||||
|
||||
.. versionadded:: 21.5
|
||||
tag (:obj:`str`): Optional. Tag of the member.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("until_date",)
|
||||
__slots__ = (
|
||||
"tag",
|
||||
"until_date",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user: User,
|
||||
until_date: dtm.datetime | None = None,
|
||||
tag: str | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
super().__init__(status=ChatMember.MEMBER, user=user, api_kwargs=api_kwargs)
|
||||
with self._unfrozen():
|
||||
self.until_date: dtm.datetime | None = until_date
|
||||
self.tag: str | None = tag
|
||||
|
||||
|
||||
class ChatMemberRestricted(ChatMember):
|
||||
@@ -490,6 +514,12 @@ class ChatMemberRestricted(ChatMember):
|
||||
notes.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
tag (:obj:`str`, optional): Tag of the member.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
status (:obj:`str`): The member's status in the chat,
|
||||
@@ -540,12 +570,19 @@ class ChatMemberRestricted(ChatMember):
|
||||
notes.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
can_edit_tag (:obj:`bool`): :obj:`True`, if the user is allowed to edit their own tag.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
tag (:obj:`str`): Optional. Tag of the member.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"can_add_web_page_previews",
|
||||
"can_change_info",
|
||||
"can_edit_tag",
|
||||
"can_invite_users",
|
||||
"can_manage_topics",
|
||||
"can_pin_messages",
|
||||
@@ -559,6 +596,7 @@ class ChatMemberRestricted(ChatMember):
|
||||
"can_send_videos",
|
||||
"can_send_voice_notes",
|
||||
"is_member",
|
||||
"tag",
|
||||
"until_date",
|
||||
)
|
||||
|
||||
@@ -581,6 +619,8 @@ class ChatMemberRestricted(ChatMember):
|
||||
can_send_videos: bool,
|
||||
can_send_video_notes: bool,
|
||||
can_send_voice_notes: bool,
|
||||
can_edit_tag: bool,
|
||||
tag: str | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -602,6 +642,8 @@ class ChatMemberRestricted(ChatMember):
|
||||
self.can_send_videos: bool = can_send_videos
|
||||
self.can_send_video_notes: bool = can_send_video_notes
|
||||
self.can_send_voice_notes: bool = can_send_voice_notes
|
||||
self.can_edit_tag: bool = can_edit_tag
|
||||
self.tag: str | None = tag
|
||||
|
||||
|
||||
class ChatMemberLeft(ChatMember):
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2026
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a chat owner change in the chat."""
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils.argumentparsing import de_json_optional
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class ChatOwnerChanged(TelegramObject):
|
||||
"""This object represents a service message about an ownership change in the chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`new_owner` is equal.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Args:
|
||||
new_owner (:class:`telegram.User`): The new owner of the chat
|
||||
|
||||
Attributes:
|
||||
new_owner (:class:`telegram.User`): The new owner of the chat
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("new_owner",)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
new_owner: User,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
super().__init__(api_kwargs=api_kwargs)
|
||||
self.new_owner: User = new_owner
|
||||
|
||||
self._id_attrs = (self.new_owner,)
|
||||
|
||||
self._freeze()
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatOwnerChanged":
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
data["new_owner"] = de_json_optional(data.get("new_owner"), User, bot)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
||||
|
||||
class ChatOwnerLeft(TelegramObject):
|
||||
"""This object represents a service message about the chat owner leaving the chat.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`new_owner` is equal.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Args:
|
||||
new_owner (:class:`telegram.User`, optional): The user which will be the new owner of the
|
||||
chat if the previous owner does not return to the chat
|
||||
|
||||
Attributes:
|
||||
new_owner (:class:`telegram.User`): Optional. The user which will be the new owner of the
|
||||
chat if the previous owner does not return to the chat
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("new_owner",)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
new_owner: User | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
super().__init__(api_kwargs=api_kwargs)
|
||||
self.new_owner: User | None = new_owner
|
||||
|
||||
self._id_attrs = (self.new_owner,)
|
||||
|
||||
self._freeze()
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatOwnerLeft":
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
data["new_owner"] = de_json_optional(data.get("new_owner"), User, bot)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
@@ -35,8 +35,8 @@ class ChatPermissions(TelegramObject):
|
||||
:attr:`can_send_polls`, :attr:`can_send_other_messages`, :attr:`can_add_web_page_previews`,
|
||||
:attr:`can_change_info`, :attr:`can_invite_users`, :attr:`can_pin_messages`,
|
||||
:attr:`can_send_audios`, :attr:`can_send_documents`, :attr:`can_send_photos`,
|
||||
:attr:`can_send_videos`, :attr:`can_send_video_notes`, :attr:`can_send_voice_notes`, and
|
||||
:attr:`can_manage_topics` are equal.
|
||||
:attr:`can_send_videos`, :attr:`can_send_video_notes`, :attr:`can_send_voice_notes`,
|
||||
:attr:`can_manage_topics` and :attr:`can_edit_tag` are equal.
|
||||
|
||||
.. versionchanged:: 20.0
|
||||
:attr:`can_manage_topics` is considered as well when comparing objects of
|
||||
@@ -47,6 +47,9 @@ class ChatPermissions(TelegramObject):
|
||||
:attr:`can_send_videos`, :attr:`can_send_video_notes` and :attr:`can_send_voice_notes`
|
||||
are considered as well when comparing objects of this type in terms of equality.
|
||||
* Removed deprecated argument and attribute ``can_send_media_messages``.
|
||||
.. versionchanged:: 22.7
|
||||
:attr:`can_edit_tag` is considered as well when comparing objects of
|
||||
this type in terms of equality.
|
||||
|
||||
|
||||
Note:
|
||||
@@ -93,6 +96,10 @@ class ChatPermissions(TelegramObject):
|
||||
notes.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
can_edit_tag (:obj:`bool`, optional): :obj:`True`, if the user is allowed to edit their own
|
||||
tag.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
can_send_messages (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to send text
|
||||
@@ -134,12 +141,17 @@ class ChatPermissions(TelegramObject):
|
||||
notes.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
can_edit_tag (:obj:`bool`): Optional. :obj:`True`, if the user is allowed to edit their own
|
||||
tag.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"can_add_web_page_previews",
|
||||
"can_change_info",
|
||||
"can_edit_tag",
|
||||
"can_invite_users",
|
||||
"can_manage_topics",
|
||||
"can_pin_messages",
|
||||
@@ -170,6 +182,7 @@ class ChatPermissions(TelegramObject):
|
||||
can_send_videos: bool | None = None,
|
||||
can_send_video_notes: bool | None = None,
|
||||
can_send_voice_notes: bool | None = None,
|
||||
can_edit_tag: bool | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -189,6 +202,7 @@ class ChatPermissions(TelegramObject):
|
||||
self.can_send_videos: bool | None = can_send_videos
|
||||
self.can_send_video_notes: bool | None = can_send_video_notes
|
||||
self.can_send_voice_notes: bool | None = can_send_voice_notes
|
||||
self.can_edit_tag: bool | None = can_edit_tag
|
||||
|
||||
self._id_attrs = (
|
||||
self.can_send_messages,
|
||||
@@ -205,6 +219,7 @@ class ChatPermissions(TelegramObject):
|
||||
self.can_send_videos,
|
||||
self.can_send_video_notes,
|
||||
self.can_send_voice_notes,
|
||||
self.can_edit_tag,
|
||||
)
|
||||
|
||||
self._freeze()
|
||||
@@ -219,7 +234,7 @@ class ChatPermissions(TelegramObject):
|
||||
.. versionadded:: 20.0
|
||||
|
||||
"""
|
||||
return cls(*(14 * (True,)))
|
||||
return cls(*(True,) * len(cls.__slots__))
|
||||
|
||||
@classmethod
|
||||
def no_permissions(cls) -> "ChatPermissions":
|
||||
@@ -229,7 +244,7 @@ class ChatPermissions(TelegramObject):
|
||||
|
||||
.. versionadded:: 20.0
|
||||
"""
|
||||
return cls(*(14 * (False,)))
|
||||
return cls(*(False,) * len(cls.__slots__))
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "ChatPermissions":
|
||||
|
||||
@@ -24,6 +24,7 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from telegram._files._basethumbedmedium import _BaseThumbedMedium
|
||||
from telegram._files.photosize import PhotoSize
|
||||
from telegram._files.videoquality import VideoQuality
|
||||
from telegram._utils.argumentparsing import de_list_optional, parse_sequence_arg, to_timedelta
|
||||
from telegram._utils.datetime import get_timedelta_value
|
||||
from telegram._utils.types import JSONDict, TimePeriod
|
||||
@@ -70,6 +71,10 @@ class Video(_BaseThumbedMedium):
|
||||
|
||||
.. versionchanged:: v22.2
|
||||
|time-period-input|
|
||||
qualities (Sequence[:class:`telegram.VideoQuality`], optional): List of available qualities
|
||||
of the video
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used to download
|
||||
@@ -100,6 +105,10 @@ class Video(_BaseThumbedMedium):
|
||||
|
||||
.. deprecated:: v22.2
|
||||
|time-period-int-deprecated|
|
||||
qualities (Sequence[:class:`telegram.VideoQuality`]): Optional. List of available qualities
|
||||
of the video
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
@@ -109,6 +118,7 @@ class Video(_BaseThumbedMedium):
|
||||
"file_name",
|
||||
"height",
|
||||
"mime_type",
|
||||
"qualities",
|
||||
"width",
|
||||
)
|
||||
|
||||
@@ -125,6 +135,7 @@ class Video(_BaseThumbedMedium):
|
||||
thumbnail: PhotoSize | None = None,
|
||||
cover: Sequence[PhotoSize] | None = None,
|
||||
start_timestamp: TimePeriod | None = None,
|
||||
qualities: Sequence[VideoQuality] | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -145,6 +156,7 @@ class Video(_BaseThumbedMedium):
|
||||
self.file_name: str | None = file_name
|
||||
self.cover: Sequence[PhotoSize] | None = parse_sequence_arg(cover)
|
||||
self._start_timestamp: dtm.timedelta | None = to_timedelta(start_timestamp)
|
||||
self.qualities: Sequence[VideoQuality] | None = parse_sequence_arg(qualities)
|
||||
|
||||
@property
|
||||
def duration(self) -> int | dtm.timedelta:
|
||||
@@ -162,5 +174,6 @@ class Video(_BaseThumbedMedium):
|
||||
data = cls._parse_data(data)
|
||||
|
||||
data["cover"] = de_list_optional(data.get("cover"), PhotoSize, bot)
|
||||
data["qualities"] = de_list_optional(data.get("qualities"), VideoQuality, bot)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2026
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram VideoQuality."""
|
||||
|
||||
from telegram._files._basemedium import _BaseMedium
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
|
||||
class VideoQuality(_BaseMedium):
|
||||
"""This object represents a video file of a specific quality.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`file_unique_id` is equal.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Args:
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used
|
||||
to download or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
width (:obj:`int`): Video width.
|
||||
height (:obj:`int`): Video height.
|
||||
codec (:obj:`str`): Codec that was used to encode the video,
|
||||
for example, ``h264``, ``h265``, or ``av01``
|
||||
file_size (:obj:`int`, optional): File size in bytes.
|
||||
|
||||
Attributes:
|
||||
file_id (:obj:`str`): Identifier for this file, which can be used
|
||||
to download or reuse the file.
|
||||
file_unique_id (:obj:`str`): Unique identifier for this file, which
|
||||
is supposed to be the same over time and for different bots.
|
||||
Can't be used to download or reuse the file.
|
||||
width (:obj:`int`): Video width.
|
||||
height (:obj:`int`): Video height.
|
||||
codec (:obj:`str`): Codec that was used to encode the video,
|
||||
for example, ``h264``, ``h265``, or ``av01``
|
||||
file_size (:obj:`int`): Optional. File size in bytes.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("codec", "height", "width")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file_id: str,
|
||||
file_unique_id: str,
|
||||
width: int,
|
||||
height: int,
|
||||
codec: str,
|
||||
file_size: int | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
super().__init__(
|
||||
file_id=file_id,
|
||||
file_unique_id=file_unique_id,
|
||||
file_size=file_size,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
with self._unfrozen():
|
||||
# Required
|
||||
self.width: int = width
|
||||
self.height: int = height
|
||||
self.codec: str = codec
|
||||
@@ -40,10 +40,11 @@ class InlineKeyboardButton(TelegramObject):
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`text`, :attr:`url`, :attr:`login_url`, :attr:`callback_data`,
|
||||
:attr:`switch_inline_query`, :attr:`switch_inline_query_current_chat`, :attr:`callback_game`,
|
||||
:attr:`web_app` and :attr:`pay` are equal.
|
||||
:attr:`web_app`, :attr:`pay`, :attr:`style` and :attr:`icon_custom_emoji_id` are equal.
|
||||
|
||||
Note:
|
||||
* Exactly one of the optional fields must be used to specify type of the button.
|
||||
* Exactly one of the fields other than :attr:`text`, :attr:`icon_custom_emoji_id`, and
|
||||
:attr:`style` must be used to specify type of the button.
|
||||
* Mind that :attr:`callback_game` is not
|
||||
working as expected. Putting a game short name in it might, but is not guaranteed to
|
||||
work.
|
||||
@@ -53,10 +54,11 @@ class InlineKeyboardButton(TelegramObject):
|
||||
associated with the button was already deleted.
|
||||
|
||||
.. versionadded:: 13.6
|
||||
|
||||
* Since Bot API 5.5, it's now allowed to mention users by their ID in inline keyboards.
|
||||
This will only work in Telegram versions released after December 7, 2021.
|
||||
Older clients will display *unsupported message*.
|
||||
* :attr:`style` option will only work in Telegram versions released after February 9, 2026.
|
||||
Older clients will display buttons without styling.
|
||||
|
||||
Warning:
|
||||
* If your bot allows your arbitrary callback data, buttons whose callback data is a
|
||||
@@ -77,6 +79,10 @@ class InlineKeyboardButton(TelegramObject):
|
||||
:attr:`web_app` is considered as well when comparing objects of this type in terms of
|
||||
equality.
|
||||
|
||||
.. versionchanged:: 22.7
|
||||
:attr:`style` and :attr:`icon_custom_emoji_id` are considered as well when
|
||||
comparing objects of this type in terms of equality.
|
||||
|
||||
Args:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
url (:obj:`str`, optional): HTTP or tg:// url to be opened when the button is pressed.
|
||||
@@ -141,6 +147,16 @@ class InlineKeyboardButton(TelegramObject):
|
||||
Note:
|
||||
This type of button **must** always be the first button in the first row and can
|
||||
only be used in invoice messages.
|
||||
style (:obj:`str`, optional): Style of the button. Must be one of
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.PRIMARY` (blue),
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.SUCCESS` (green), and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.DANGER` (red).
|
||||
Color name aliases :tg-const:`telegram.constants.KeyboardButtonStyle.BLUE`,
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.GREEN`, and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.RED` are also available.
|
||||
If omitted, then an app-specific style is used.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
switch_inline_query_chosen_chat (:class:`telegram.SwitchInlineQueryChosenChat`, optional):
|
||||
If set, pressing the button will prompt the user to select one of their chats of the
|
||||
specified type, open that chat and insert the bot's username and the specified inline
|
||||
@@ -156,6 +172,13 @@ class InlineKeyboardButton(TelegramObject):
|
||||
Caution:
|
||||
The PTB team has discovered that this field works correctly only if your Telegram
|
||||
client is released after April 20th 2023.
|
||||
icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the custom emoji shown
|
||||
before the text of the button. Can only be used by bots that purchased additional
|
||||
usernames on `Fragment <https://fragment.com/>`_ or in the messages directly sent by
|
||||
the bot to private, group and supergroup chats if the owner of the bot has a Telegram
|
||||
Premium subscription.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
text (:obj:`str`): Label text on the button.
|
||||
@@ -202,6 +225,16 @@ class InlineKeyboardButton(TelegramObject):
|
||||
copies the specified text to the clipboard.
|
||||
|
||||
.. versionadded:: 21.7
|
||||
style (:obj:`str`): Optional. Style of the button. Must be one of
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.PRIMARY` (blue),
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.SUCCESS` (green), and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.DANGER` (red).
|
||||
Color name aliases :tg-const:`telegram.constants.KeyboardButtonStyle.BLUE`,
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.GREEN`, and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.RED` are also available.
|
||||
If omitted, then an app-specific style is used.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
callback_game (:class:`telegram.CallbackGame`): Optional. Description of the game that will
|
||||
be launched when the user presses the button.
|
||||
|
||||
@@ -229,14 +262,23 @@ class InlineKeyboardButton(TelegramObject):
|
||||
Caution:
|
||||
The PTB team has discovered that this field works correctly only if your Telegram
|
||||
client is released after April 20th 2023.
|
||||
icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the custom emoji shown
|
||||
before the text of the button. Can only be used by bots that purchased additional
|
||||
usernames on `Fragment <https://fragment.com/>`_ or in the messages directly sent by
|
||||
the bot to private, group and supergroup chats if the owner of the bot has a Telegram
|
||||
Premium subscription.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"callback_data",
|
||||
"callback_game",
|
||||
"copy_text",
|
||||
"icon_custom_emoji_id",
|
||||
"login_url",
|
||||
"pay",
|
||||
"style",
|
||||
"switch_inline_query",
|
||||
"switch_inline_query_chosen_chat",
|
||||
"switch_inline_query_current_chat",
|
||||
@@ -258,6 +300,8 @@ class InlineKeyboardButton(TelegramObject):
|
||||
web_app: WebAppInfo | None = None,
|
||||
switch_inline_query_chosen_chat: SwitchInlineQueryChosenChat | None = None,
|
||||
copy_text: CopyTextButton | None = None,
|
||||
style: str | None = None,
|
||||
icon_custom_emoji_id: str | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -278,6 +322,8 @@ class InlineKeyboardButton(TelegramObject):
|
||||
switch_inline_query_chosen_chat
|
||||
)
|
||||
self.copy_text: CopyTextButton | None = copy_text
|
||||
self.style: str | None = style
|
||||
self.icon_custom_emoji_id: str | None = icon_custom_emoji_id
|
||||
self._id_attrs = ()
|
||||
self._set_id_attrs()
|
||||
|
||||
@@ -294,6 +340,8 @@ class InlineKeyboardButton(TelegramObject):
|
||||
self.switch_inline_query_current_chat,
|
||||
self.callback_game,
|
||||
self.pay,
|
||||
self.style,
|
||||
self.icon_custom_emoji_id,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -34,13 +34,14 @@ if TYPE_CHECKING:
|
||||
class KeyboardButton(TelegramObject):
|
||||
"""
|
||||
This object represents one button of the reply keyboard. At most one of the optional fields
|
||||
must be used to specify type of the button. For simple text buttons, :obj:`str`
|
||||
other than :attr:`text`, :attr:`icon_custom_emoji_id`, and :attr:`style` must be used to
|
||||
specify the type of the button. For simple text buttons, :obj:`str`
|
||||
can be used instead of this object to specify text of the button.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`text`, :attr:`request_contact`, :attr:`request_location`,
|
||||
:attr:`request_poll`, :attr:`web_app`, :attr:`request_users` and :attr:`request_chat` are
|
||||
equal.
|
||||
:attr:`request_poll`, :attr:`web_app`, :attr:`request_users`, :attr:`request_chat`,
|
||||
:attr:`style` and :attr:`icon_custom_emoji_id` are equal.
|
||||
|
||||
Note:
|
||||
* Optional fields are mutually exclusive.
|
||||
@@ -53,6 +54,8 @@ class KeyboardButton(TelegramObject):
|
||||
* :attr:`request_users` and :attr:`request_chat` options will only work in Telegram
|
||||
versions released after 3 February, 2023. Older clients will display unsupported
|
||||
message.
|
||||
* :attr:`style` option will only work in Telegram versions released after February 9, 2026.
|
||||
Older clients will display buttons without styling.
|
||||
|
||||
.. versionchanged:: 21.0
|
||||
Removed deprecated argument and attribute ``request_user``.
|
||||
@@ -62,10 +65,18 @@ class KeyboardButton(TelegramObject):
|
||||
.. versionchanged:: 20.5
|
||||
:attr:`request_users` and :attr:`request_chat` are considered as well when
|
||||
comparing objects of this type in terms of equality.
|
||||
.. versionchanged:: 22.7
|
||||
:attr:`icon_custom_emoji_id` is considered as well when comparing objects of this type in
|
||||
terms of equality.
|
||||
|
||||
.. versionchanged:: 22.7
|
||||
:attr:`style` and :attr:`icon_custom_emoji_id` are considered as well when
|
||||
comparing objects of this type in terms of equality.
|
||||
|
||||
Args:
|
||||
text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be
|
||||
sent to the bot as a message when the button is pressed.
|
||||
text (:obj:`str`): Text of the button. If none of the fields other than :attr:`text`,
|
||||
:attr:`icon_custom_emoji_id`, and :attr:`style` are used, it will be sent as a
|
||||
message when the button is pressed.
|
||||
request_contact (:obj:`bool`, optional): If :obj:`True`, the user's phone number will be
|
||||
sent as a contact when the button is pressed. Available in private chats only.
|
||||
request_location (:obj:`bool`, optional): If :obj:`True`, the user's current location will
|
||||
@@ -92,9 +103,28 @@ class KeyboardButton(TelegramObject):
|
||||
Available in private chats only.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
style (:obj:`str`, optional): Style of the button. Must be one of
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.PRIMARY` (blue),
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.SUCCESS` (green), and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.DANGER` (red).
|
||||
Color name aliases :tg-const:`telegram.constants.KeyboardButtonStyle.BLUE`,
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.GREEN`, and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.RED` are also available.
|
||||
If omitted, then an app-specific style is used.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
icon_custom_emoji_id (:obj:`str`, optional): Unique identifier of the
|
||||
custom emoji shown before the text of the button. Can only be used by bots that
|
||||
purchased additional usernames on Fragment or in the messages directly sent by the
|
||||
bot to private, group and supergroup chats if the owner of the bot has a Telegram
|
||||
Premium subscription.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
text (:obj:`str`): Text of the button. If none of the optional fields are used, it will be
|
||||
sent to the bot as a message when the button is pressed.
|
||||
text (:obj:`str`): Text of the button. If none of the fields other than :attr:`text`,
|
||||
:attr:`icon_custom_emoji_id`, and :attr:`style` are used, it will be sent as a
|
||||
message when the button is pressed.
|
||||
request_contact (:obj:`bool`): Optional. If :obj:`True`, the user's phone number will be
|
||||
sent as a contact when the button is pressed. Available in private chats only.
|
||||
request_location (:obj:`bool`): Optional. If :obj:`True`, the user's current location will
|
||||
@@ -120,14 +150,33 @@ class KeyboardButton(TelegramObject):
|
||||
Available in private chats only.
|
||||
|
||||
.. versionadded:: 20.1
|
||||
style (:obj:`str`): Optional. Style of the button. Must be one of
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.PRIMARY` (blue),
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.SUCCESS` (green), and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.DANGER` (red).
|
||||
Color name aliases :tg-const:`telegram.constants.KeyboardButtonStyle.BLUE`,
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.GREEN`, and
|
||||
:tg-const:`telegram.constants.KeyboardButtonStyle.RED` are also available.
|
||||
If omitted, then an app-specific style is used.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
icon_custom_emoji_id (:obj:`str`): Optional. Unique identifier of the
|
||||
custom emoji shown before the text of the button. Can only be used by bots that
|
||||
purchased additional usernames on Fragment or in the messages directly sent by the
|
||||
bot to private, group and supergroup chats if the owner of the bot has a Telegram
|
||||
Premium subscription.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"icon_custom_emoji_id",
|
||||
"request_chat",
|
||||
"request_contact",
|
||||
"request_location",
|
||||
"request_poll",
|
||||
"request_users",
|
||||
"style",
|
||||
"text",
|
||||
"web_app",
|
||||
)
|
||||
@@ -141,6 +190,8 @@ class KeyboardButton(TelegramObject):
|
||||
web_app: WebAppInfo | None = None,
|
||||
request_chat: KeyboardButtonRequestChat | None = None,
|
||||
request_users: KeyboardButtonRequestUsers | None = None,
|
||||
style: str | None = None,
|
||||
icon_custom_emoji_id: str | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -155,6 +206,8 @@ class KeyboardButton(TelegramObject):
|
||||
self.web_app: WebAppInfo | None = web_app
|
||||
self.request_users: KeyboardButtonRequestUsers | None = request_users
|
||||
self.request_chat: KeyboardButtonRequestChat | None = request_chat
|
||||
self.style: str | None = style
|
||||
self.icon_custom_emoji_id: str | None = icon_custom_emoji_id
|
||||
|
||||
self._id_attrs = (
|
||||
self.text,
|
||||
@@ -164,6 +217,8 @@ class KeyboardButton(TelegramObject):
|
||||
self.web_app,
|
||||
self.request_users,
|
||||
self.request_chat,
|
||||
self.style,
|
||||
self.icon_custom_emoji_id,
|
||||
)
|
||||
|
||||
self._freeze()
|
||||
|
||||
@@ -28,6 +28,7 @@ from typing import TYPE_CHECKING, TypedDict
|
||||
from telegram._chat import Chat
|
||||
from telegram._chatbackground import ChatBackground
|
||||
from telegram._chatboost import ChatBoostAdded
|
||||
from telegram._chatowner import ChatOwnerChanged, ChatOwnerLeft
|
||||
from telegram._checklists import Checklist, ChecklistTasksAdded, ChecklistTasksDone
|
||||
from telegram._dice import Dice
|
||||
from telegram._directmessagepricechanged import DirectMessagePriceChanged
|
||||
@@ -73,7 +74,7 @@ from telegram._telegramobject import TelegramObject
|
||||
from telegram._uniquegift import UniqueGiftInfo
|
||||
from telegram._user import User
|
||||
from telegram._utils.argumentparsing import de_json_optional, de_list_optional, parse_sequence_arg
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp, to_timestamp
|
||||
from telegram._utils.defaultvalue import DEFAULT_NONE, DefaultValue
|
||||
from telegram._utils.entities import parse_message_entities, parse_message_entity
|
||||
from telegram._utils.strings import TextEncoding
|
||||
@@ -329,8 +330,8 @@ class Message(MaybeInaccessibleMessage):
|
||||
or as a scheduled message.
|
||||
|
||||
.. versionadded:: 21.1
|
||||
media_group_id (:obj:`str`, optional): The unique identifier of a media message group this
|
||||
message belongs to.
|
||||
media_group_id (:obj:`str`, optional): The unique identifier inside this chat of a media
|
||||
message group this message belongs to.
|
||||
text (:obj:`str`, optional): For text messages, the actual UTF-8 text of the message,
|
||||
0-:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters.
|
||||
entities (Sequence[:class:`telegram.MessageEntity`], optional): For text messages, special
|
||||
@@ -676,6 +677,18 @@ class Message(MaybeInaccessibleMessage):
|
||||
task that is being replied to.
|
||||
|
||||
.. versionadded:: 22.4
|
||||
chat_owner_left (:class:`telegram.ChatOwnerLeft`, optional): Service message: chat owner
|
||||
has left.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
chat_owner_changed (:class:`telegram.ChatOwnerChanged`, optional): Service message: chat
|
||||
owner has changed.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
sender_tag (:obj:`str`, optional): Tag or custom title of the sender of the message; for
|
||||
supergroups only
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
message_id (:obj:`int`): Unique message identifier inside this chat. In specific instances
|
||||
@@ -717,8 +730,8 @@ class Message(MaybeInaccessibleMessage):
|
||||
or as a scheduled message.
|
||||
|
||||
.. versionadded:: 21.1
|
||||
media_group_id (:obj:`str`): Optional. The unique identifier of a media message group this
|
||||
message belongs to.
|
||||
media_group_id (:obj:`str`): Optional. The unique identifier inside this chat of a media
|
||||
message group this message belongs to.
|
||||
text (:obj:`str`): Optional. For text messages, the actual UTF-8 text of the message,
|
||||
0-:tg-const:`telegram.constants.MessageLimit.MAX_TEXT_LENGTH` characters.
|
||||
entities (tuple[:class:`telegram.MessageEntity`]): Optional. For text messages, special
|
||||
@@ -1080,6 +1093,18 @@ class Message(MaybeInaccessibleMessage):
|
||||
task that is being replied to.
|
||||
|
||||
.. versionadded:: 22.4
|
||||
chat_owner_left (:class:`telegram.ChatOwnerLeft`): Optional. Service message: chat owner
|
||||
has left.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
chat_owner_changed (:class:`telegram.ChatOwnerChanged`): Optional. Service message: chat
|
||||
owner has changed.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
sender_tag (:obj:`str`): Optional. Tag or custom title of the sender of the message; for
|
||||
supergroups only
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
.. |custom_emoji_no_md1_support| replace:: Since custom emoji entities are not supported by
|
||||
:attr:`~telegram.constants.ParseMode.MARKDOWN`, this method now raises a
|
||||
@@ -1108,6 +1133,8 @@ class Message(MaybeInaccessibleMessage):
|
||||
"caption_entities",
|
||||
"channel_chat_created",
|
||||
"chat_background_set",
|
||||
"chat_owner_changed",
|
||||
"chat_owner_left",
|
||||
"chat_shared",
|
||||
"checklist",
|
||||
"checklist_tasks_added",
|
||||
@@ -1174,6 +1201,7 @@ class Message(MaybeInaccessibleMessage):
|
||||
"sender_boost_count",
|
||||
"sender_business_bot",
|
||||
"sender_chat",
|
||||
"sender_tag",
|
||||
"show_caption_above_media",
|
||||
"sticker",
|
||||
"story",
|
||||
@@ -1306,6 +1334,9 @@ class Message(MaybeInaccessibleMessage):
|
||||
suggested_post_approved: "SuggestedPostApproved | None" = None,
|
||||
suggested_post_approval_failed: "SuggestedPostApprovalFailed | None" = None,
|
||||
gift_upgrade_sent: GiftInfo | None = None,
|
||||
chat_owner_changed: ChatOwnerChanged | None = None,
|
||||
chat_owner_left: ChatOwnerLeft | None = None,
|
||||
sender_tag: str | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -1433,6 +1464,9 @@ class Message(MaybeInaccessibleMessage):
|
||||
suggested_post_approval_failed
|
||||
)
|
||||
self.gift_upgrade_sent: GiftInfo | None = gift_upgrade_sent
|
||||
self.chat_owner_changed: ChatOwnerChanged | None = chat_owner_changed
|
||||
self.chat_owner_left: ChatOwnerLeft | None = chat_owner_left
|
||||
self.sender_tag: str | None = sender_tag
|
||||
|
||||
self._effective_attachment = DEFAULT_NONE
|
||||
|
||||
@@ -1462,8 +1496,8 @@ class Message(MaybeInaccessibleMessage):
|
||||
to the corresponding thread view.
|
||||
"""
|
||||
if self.chat.type not in [Chat.PRIVATE, Chat.GROUP]:
|
||||
# the else block gets rid of leading -100 for supergroups:
|
||||
to_link = self.chat.username if self.chat.username else f"c/{str(self.chat.id)[4:]}"
|
||||
# if username doesn't exist, remove the leading -100 for supergroups in link
|
||||
to_link = self.chat.username or f"c/{str(self.chat.id)[4:]}"
|
||||
baselink = f"https://t.me/{to_link}/{self.message_id}"
|
||||
|
||||
# adds the thread for topics and replies
|
||||
@@ -1649,6 +1683,10 @@ class Message(MaybeInaccessibleMessage):
|
||||
data.get("suggested_post_approval_failed"), SuggestedPostApprovalFailed, bot
|
||||
)
|
||||
data["gift_upgrade_sent"] = de_json_optional(data.get("gift_upgrade_sent"), GiftInfo, bot)
|
||||
data["chat_owner_changed"] = de_json_optional(
|
||||
data.get("chat_owner_changed"), ChatOwnerChanged, bot
|
||||
)
|
||||
data["chat_owner_left"] = de_json_optional(data.get("chat_owner_left"), ChatOwnerLeft, bot)
|
||||
|
||||
api_kwargs = {}
|
||||
# This is a deprecated field that TG still returns for backwards compatibility
|
||||
@@ -5277,6 +5315,17 @@ class Message(MaybeInaccessibleMessage):
|
||||
insert = f'<span class="tg-spoiler">{escaped_text}</span>'
|
||||
elif entity.type == MessageEntity.CUSTOM_EMOJI:
|
||||
insert = f'<tg-emoji emoji-id="{entity.custom_emoji_id}">{escaped_text}</tg-emoji>'
|
||||
elif entity.type == MessageEntity.DATE_TIME:
|
||||
if entity.date_time_format:
|
||||
insert = (
|
||||
f'<tg-time unix="{to_timestamp(entity.unix_time)}" '
|
||||
f'format="{entity.date_time_format}">{escaped_text}</tg-time>'
|
||||
)
|
||||
else:
|
||||
insert = (
|
||||
f'<tg-time unix="{to_timestamp(entity.unix_time)}">'
|
||||
f"{escaped_text}</tg-time>"
|
||||
)
|
||||
else:
|
||||
insert = escaped_text
|
||||
|
||||
@@ -5416,6 +5465,7 @@ class Message(MaybeInaccessibleMessage):
|
||||
MessageEntity.SPOILER,
|
||||
MessageEntity.STRIKETHROUGH,
|
||||
MessageEntity.UNDERLINE,
|
||||
MessageEntity.DATE_TIME,
|
||||
):
|
||||
if any(entity.type == entity_type for entity in entities):
|
||||
name = entity_type.name.title().replace("_", " ") # type:ignore[attr-defined]
|
||||
@@ -5508,6 +5558,14 @@ class Message(MaybeInaccessibleMessage):
|
||||
entity_type=MessageEntity.CUSTOM_EMOJI,
|
||||
)
|
||||
insert = f""
|
||||
elif entity.type == MessageEntity.DATE_TIME:
|
||||
if entity.date_time_format:
|
||||
insert = (
|
||||
f"}"
|
||||
f"&format={entity.date_time_format})"
|
||||
)
|
||||
else:
|
||||
insert = f"})"
|
||||
else:
|
||||
insert = escaped_text
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"""This module contains an object that represents a Telegram MessageEntity."""
|
||||
|
||||
import copy
|
||||
import datetime as dtm
|
||||
import itertools
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING, Final
|
||||
@@ -28,6 +29,7 @@ from telegram._telegramobject import TelegramObject
|
||||
from telegram._user import User
|
||||
from telegram._utils import enum
|
||||
from telegram._utils.argumentparsing import de_json_optional
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.strings import TextEncoding
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
@@ -55,13 +57,17 @@ class MessageEntity(TelegramObject):
|
||||
(underlined text), :attr:`STRIKETHROUGH`, :attr:`SPOILER` (spoiler message),
|
||||
:attr:`BLOCKQUOTE` (block quotation), :attr:`CODE` (monowidth string), :attr:`PRE`
|
||||
(monowidth block), :attr:`TEXT_LINK` (for clickable text URLs), :attr:`TEXT_MENTION`
|
||||
(for users without usernames), :attr:`CUSTOM_EMOJI` (for inline custom emoji stickers).
|
||||
(for users without usernames), :attr:`CUSTOM_EMOJI` (for inline custom emoji stickers)
|
||||
or :attr:`DATE_TIME` (for formatted date and time).
|
||||
|
||||
.. versionadded:: 20.0
|
||||
Added inline custom emoji
|
||||
|
||||
.. versionadded:: 20.8
|
||||
Added block quotation
|
||||
|
||||
.. versionadded:: 22.7
|
||||
Added date_time
|
||||
offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity.
|
||||
length (:obj:`int`): Length of the entity in UTF-16 code units.
|
||||
url (:obj:`str`, optional): For :attr:`TEXT_LINK` only, url that will be opened after
|
||||
@@ -75,6 +81,17 @@ class MessageEntity(TelegramObject):
|
||||
information about the sticker.
|
||||
|
||||
.. versionadded:: 20.0
|
||||
date_time_format (:obj:`str`, optional): For :attr:`DATE_TIME` only, the string that
|
||||
defines the formatting of the date and time. See `date-time entity formatting
|
||||
<https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details and
|
||||
:class:`telegram.constants.MessageEntityDateTimeFormats` for all possible formats.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
unix_time (:class:`datetime.datetime`, optional): For :attr:`DATE_TIME` only, the time
|
||||
associated with the entity.
|
||||
|datetime_localization|
|
||||
|
||||
.. versionadded:: 22.7
|
||||
Attributes:
|
||||
type (:obj:`str`): Type of the entity. Can be :attr:`MENTION` (``@username``),
|
||||
:attr:`HASHTAG` (``#hashtag`` or ``#hashtag@chatusername``), :attr:`CASHTAG` (``$USD``
|
||||
@@ -85,13 +102,17 @@ class MessageEntity(TelegramObject):
|
||||
(underlined text), :attr:`STRIKETHROUGH`, :attr:`SPOILER` (spoiler message),
|
||||
:attr:`BLOCKQUOTE` (block quotation), :attr:`CODE` (monowidth string), :attr:`PRE`
|
||||
(monowidth block), :attr:`TEXT_LINK` (for clickable text URLs), :attr:`TEXT_MENTION`
|
||||
(for users without usernames), :attr:`CUSTOM_EMOJI` (for inline custom emoji stickers).
|
||||
(for users without usernames), :attr:`CUSTOM_EMOJI` (for inline custom emoji stickers)
|
||||
or :attr:`DATE_TIME` (for formatted date and time).
|
||||
|
||||
.. versionadded:: 20.0
|
||||
Added inline custom emoji
|
||||
|
||||
.. versionadded:: 20.8
|
||||
Added block quotation
|
||||
|
||||
.. versionadded:: 22.7
|
||||
Added date_time
|
||||
offset (:obj:`int`): Offset in UTF-16 code units to the start of the entity.
|
||||
length (:obj:`int`): Length of the entity in UTF-16 code units.
|
||||
url (:obj:`str`): Optional. For :attr:`TEXT_LINK` only, url that will be opened after
|
||||
@@ -105,10 +126,31 @@ class MessageEntity(TelegramObject):
|
||||
information about the sticker.
|
||||
|
||||
.. versionadded:: 20.0
|
||||
date_time_format (:obj:`str`): Optional. For :attr:`DATE_TIME` only, the string that
|
||||
defines the formatting of the date and time. See `date-time entity formatting
|
||||
<https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details and
|
||||
:class:`telegram.constants.MessageEntityDateTimeFormats` for all possible formats.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
unix_time (:class:`datetime.datetime`): Optional. For :attr:`DATE_TIME` only, the time
|
||||
associated with the entity.
|
||||
|datetime_localization|
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("custom_emoji_id", "language", "length", "offset", "type", "url", "user")
|
||||
__slots__ = (
|
||||
"custom_emoji_id",
|
||||
"date_time_format",
|
||||
"language",
|
||||
"length",
|
||||
"offset",
|
||||
"type",
|
||||
"unix_time",
|
||||
"url",
|
||||
"user",
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -119,6 +161,8 @@ class MessageEntity(TelegramObject):
|
||||
user: User | None = None,
|
||||
language: str | None = None,
|
||||
custom_emoji_id: str | None = None,
|
||||
date_time_format: str | None = None,
|
||||
unix_time: dtm.datetime | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -132,6 +176,8 @@ class MessageEntity(TelegramObject):
|
||||
self.user: User | None = user
|
||||
self.language: str | None = language
|
||||
self.custom_emoji_id: str | None = custom_emoji_id
|
||||
self.date_time_format: str | None = date_time_format
|
||||
self.unix_time: dtm.datetime | None = unix_time
|
||||
|
||||
self._id_attrs = (self.type, self.offset, self.length)
|
||||
|
||||
@@ -144,6 +190,10 @@ class MessageEntity(TelegramObject):
|
||||
|
||||
data["user"] = de_json_optional(data.get("user"), User, bot)
|
||||
|
||||
# Get the local timezone from the bot if it has defaults
|
||||
loc_tzinfo = extract_tzinfo_from_defaults(bot)
|
||||
data["unix_time"] = from_timestamp(data.get("unix_time"), tzinfo=loc_tzinfo)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
|
||||
@staticmethod
|
||||
@@ -376,6 +426,11 @@ class MessageEntity(TelegramObject):
|
||||
|
||||
.. versionadded:: 20.0
|
||||
"""
|
||||
DATE_TIME: Final[str] = constants.MessageEntityType.DATE_TIME
|
||||
""":const:`telegram.constants.MessageEntityType.DATE_TIME`
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
EMAIL: Final[str] = constants.MessageEntityType.EMAIL
|
||||
""":const:`telegram.constants.MessageEntityType.EMAIL`"""
|
||||
EXPANDABLE_BLOCKQUOTE: Final[str] = constants.MessageEntityType.EXPANDABLE_BLOCKQUOTE
|
||||
|
||||
+37
-59
@@ -31,12 +31,6 @@ from telegram._utils import enum
|
||||
from telegram._utils.argumentparsing import de_json_optional, parse_sequence_arg
|
||||
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
|
||||
from telegram._utils.types import JSONDict
|
||||
from telegram._utils.warnings import warn
|
||||
from telegram._utils.warnings_transition import (
|
||||
build_deprecation_warning_message,
|
||||
warn_about_deprecated_attr_in_property,
|
||||
)
|
||||
from telegram.warnings import PTBDeprecationWarning
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
@@ -128,18 +122,32 @@ class UniqueGiftModel(TelegramObject):
|
||||
name (:obj:`str`): Name of the model.
|
||||
sticker (:class:`telegram.Sticker`): The sticker that represents the unique gift.
|
||||
rarity_per_mille (:obj:`int`): The number of unique gifts that receive this
|
||||
model for every ``1000`` gifts upgraded.
|
||||
model for every ``1000`` gifts upgraded. Always ``0`` for crafted gifts.
|
||||
rarity (:obj:`str`, optional): Rarity of the model if it is a crafted model.
|
||||
Currently, can be :tg-const:`telegram.constants.UniqueGiftModelRarity.UNCOMMON`,
|
||||
:tg-const:`telegram.constants.UniqueGiftModelRarity.RARE`,
|
||||
:tg-const:`telegram.constants.UniqueGiftModelRarity.EPIC`,
|
||||
or :tg-const:`telegram.constants.UniqueGiftModelRarity.LEGENDARY`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
name (:obj:`str`): Name of the model.
|
||||
sticker (:class:`telegram.Sticker`): The sticker that represents the unique gift.
|
||||
rarity_per_mille (:obj:`int`): The number of unique gifts that receive this
|
||||
model for every ``1000`` gifts upgraded.
|
||||
model for every ``1000`` gifts upgraded. Always ``0`` for crafted gifts.
|
||||
rarity (:obj:`str`): Optional. Rarity of the model if it is a crafted model.
|
||||
Currently, can be :tg-const:`telegram.constants.UniqueGiftModelRarity.UNCOMMON`,
|
||||
:tg-const:`telegram.constants.UniqueGiftModelRarity.RARE`,
|
||||
:tg-const:`telegram.constants.UniqueGiftModelRarity.EPIC`,
|
||||
or :tg-const:`telegram.constants.UniqueGiftModelRarity.LEGENDARY`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
"name",
|
||||
"rarity",
|
||||
"rarity_per_mille",
|
||||
"sticker",
|
||||
)
|
||||
@@ -149,13 +157,17 @@ class UniqueGiftModel(TelegramObject):
|
||||
name: str,
|
||||
sticker: Sticker,
|
||||
rarity_per_mille: int,
|
||||
rarity: str | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
super().__init__(api_kwargs=api_kwargs)
|
||||
# Required
|
||||
self.name: str = name
|
||||
self.sticker: Sticker = sticker
|
||||
self.rarity_per_mille: int = rarity_per_mille
|
||||
# Optional
|
||||
self.rarity: str | None = enum.get_member(constants.UniqueGiftModelRarity, rarity, rarity)
|
||||
|
||||
self._id_attrs = (self.name, self.sticker, self.rarity_per_mille)
|
||||
|
||||
@@ -340,6 +352,9 @@ class UniqueGift(TelegramObject):
|
||||
|
||||
.. versionadded:: 22.1
|
||||
|
||||
.. versionchanged:: 22.7
|
||||
:attr:`gift_id` is now a positional argument.
|
||||
|
||||
Args:
|
||||
gift_id (:obj:`str`): Identifier of the regular gift from which the gift was upgraded.
|
||||
|
||||
@@ -370,6 +385,10 @@ class UniqueGift(TelegramObject):
|
||||
business account gifts and gifts that are currently on sale only.
|
||||
|
||||
.. versionadded:: 22.6
|
||||
is_burned (:obj:`bool`, optional): :obj:`True`, if the gift was used to craft another
|
||||
gift and isn't available anymore.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
gift_id (:obj:`str`): Identifier of the regular gift from which the gift was upgraded.
|
||||
@@ -401,7 +420,10 @@ class UniqueGift(TelegramObject):
|
||||
business account gifts and gifts that are currently on sale only.
|
||||
|
||||
.. versionadded:: 22.6
|
||||
is_burned (:obj:`bool`): Optional. :obj:`True`, if the gift was used to craft another
|
||||
gift and isn't available anymore.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
@@ -409,6 +431,7 @@ class UniqueGift(TelegramObject):
|
||||
"base_name",
|
||||
"colors",
|
||||
"gift_id",
|
||||
"is_burned",
|
||||
"is_from_blockchain",
|
||||
"is_premium",
|
||||
"model",
|
||||
@@ -420,6 +443,7 @@ class UniqueGift(TelegramObject):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
gift_id: str,
|
||||
base_name: str,
|
||||
name: str,
|
||||
number: int,
|
||||
@@ -427,19 +451,13 @@ class UniqueGift(TelegramObject):
|
||||
symbol: UniqueGiftSymbol,
|
||||
backdrop: UniqueGiftBackdrop,
|
||||
publisher_chat: Chat | None = None,
|
||||
# tags: deprecated 22.6, bot api 9.3
|
||||
# temporarily optional to account for changed signature
|
||||
gift_id: str | None = None,
|
||||
is_from_blockchain: bool | None = None,
|
||||
is_premium: bool | None = None,
|
||||
colors: UniqueGiftColors | None = None,
|
||||
is_burned: bool | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
# tags: deprecated 22.6, bot api 9.3
|
||||
if gift_id is None:
|
||||
raise TypeError("`gift_id` is a required argument since Bot API 9.3")
|
||||
|
||||
super().__init__(api_kwargs=api_kwargs)
|
||||
self.gift_id: str = gift_id
|
||||
self.base_name: str = base_name
|
||||
@@ -452,6 +470,7 @@ class UniqueGift(TelegramObject):
|
||||
self.is_from_blockchain: bool | None = is_from_blockchain
|
||||
self.is_premium: bool | None = is_premium
|
||||
self.colors: UniqueGiftColors | None = colors
|
||||
self.is_burned: bool | None = is_burned
|
||||
|
||||
self._id_attrs = (
|
||||
self.base_name,
|
||||
@@ -486,6 +505,9 @@ class UniqueGiftInfo(TelegramObject):
|
||||
|
||||
.. versionadded:: 22.1
|
||||
|
||||
.. versionremoved:: 22.7
|
||||
Removed argument and attribute ``last_resale_star_count`` deprecated since Bot API 9.3.
|
||||
|
||||
Args:
|
||||
gift (:class:`UniqueGift`): Information about the gift.
|
||||
origin (:obj:`str`): Origin of the gift. Currently, either :attr:`UPGRADE` for gifts
|
||||
@@ -502,13 +524,6 @@ class UniqueGiftInfo(TelegramObject):
|
||||
bot; only present for gifts received on behalf of business accounts.
|
||||
transfer_star_count (:obj:`int`, optional): Number of Telegram Stars that must be paid
|
||||
to transfer the gift; omitted if the bot cannot transfer the gift.
|
||||
last_resale_star_count (:obj:`int`, optional): For gifts bought from other users, the price
|
||||
paid for the gift.
|
||||
|
||||
.. versionadded:: 22.3
|
||||
.. deprecated:: 22.6
|
||||
Bot API 9.3 deprecated this field. Use :attr:`last_resale_currency` and
|
||||
:attr:`last_resale_amount` instead.
|
||||
last_resale_currency (:obj:`str`, optional): For gifts bought from other users, the
|
||||
currency in which the payment for the gift was done. Currently, one of ``XTR`` for
|
||||
Telegram Stars or ``TON`` for toncoins.
|
||||
@@ -577,7 +592,6 @@ class UniqueGiftInfo(TelegramObject):
|
||||
""":const:`telegram.constants.UniqueGiftInfoOrigin.UPGRADE`"""
|
||||
|
||||
__slots__ = (
|
||||
"_last_resale_star_count",
|
||||
"gift",
|
||||
"last_resale_amount",
|
||||
"last_resale_currency",
|
||||
@@ -593,28 +607,12 @@ class UniqueGiftInfo(TelegramObject):
|
||||
origin: str,
|
||||
owned_gift_id: str | None = None,
|
||||
transfer_star_count: int | None = None,
|
||||
# tags: deprecated 22.6; bot api 9.3
|
||||
last_resale_star_count: int | None = None,
|
||||
next_transfer_date: dtm.datetime | None = None,
|
||||
last_resale_currency: str | None = None,
|
||||
last_resale_amount: int | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
if last_resale_star_count is not None:
|
||||
warn(
|
||||
PTBDeprecationWarning(
|
||||
version="22.6",
|
||||
message=build_deprecation_warning_message(
|
||||
deprecated_name="last_resale_star_count",
|
||||
new_name="last_resale_currency/amount",
|
||||
bot_api_version="9.3",
|
||||
object_type="parameter",
|
||||
),
|
||||
),
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
super().__init__(api_kwargs=api_kwargs)
|
||||
# Required
|
||||
self.gift: UniqueGift = gift
|
||||
@@ -622,7 +620,6 @@ class UniqueGiftInfo(TelegramObject):
|
||||
# Optional
|
||||
self.owned_gift_id: str | None = owned_gift_id
|
||||
self.transfer_star_count: int | None = transfer_star_count
|
||||
self._last_resale_star_count: int | None = last_resale_star_count
|
||||
self.next_transfer_date: dtm.datetime | None = next_transfer_date
|
||||
self.last_resale_currency: str | None = last_resale_currency
|
||||
self.last_resale_amount: int | None = last_resale_amount
|
||||
@@ -631,25 +628,6 @@ class UniqueGiftInfo(TelegramObject):
|
||||
|
||||
self._freeze()
|
||||
|
||||
# tags: deprecated 22.6; bot api 9.3
|
||||
@property
|
||||
def last_resale_star_count(self) -> int | None:
|
||||
""":obj:`int`: Optional. For gifts bought from other users, the price
|
||||
paid for the gift.
|
||||
|
||||
.. versionadded:: 22.3
|
||||
.. deprecated:: 22.6
|
||||
Bot API 9.3 deprecated this field. Use :attr:`last_resale_currency` and
|
||||
:attr:`last_resale_amount` instead.
|
||||
"""
|
||||
warn_about_deprecated_attr_in_property(
|
||||
deprecated_attr_name="last_resale_star_count",
|
||||
new_attr_name="last_resale_currency/amount",
|
||||
bot_api_version="9.3",
|
||||
ptb_version="22.6",
|
||||
)
|
||||
return self._last_resale_star_count
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UniqueGiftInfo":
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
|
||||
@@ -63,6 +63,7 @@ if TYPE_CHECKING:
|
||||
Story,
|
||||
SuggestedPostParameters,
|
||||
UserChatBoosts,
|
||||
UserProfileAudios,
|
||||
UserProfilePhotos,
|
||||
Venue,
|
||||
Video,
|
||||
@@ -118,6 +119,11 @@ class User(TelegramObject):
|
||||
enabled in private chats. Returned only in :meth:`telegram.Bot.get_me`.
|
||||
|
||||
.. versionadded:: 22.6
|
||||
allows_users_to_create_topics (:obj:`bool`, optional): :obj:`True`, if the bot allows
|
||||
users to create and delete topics in private chats. Returned only in
|
||||
:meth:`telegram.Bot.get_me`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Attributes:
|
||||
id (:obj:`int`): Unique identifier for this user or bot.
|
||||
@@ -153,6 +159,11 @@ class User(TelegramObject):
|
||||
enabled in private chats. Returned only in :meth:`telegram.Bot.get_me`.
|
||||
|
||||
.. versionadded:: 22.6
|
||||
allows_users_to_create_topics (:obj:`bool`): Optional. :obj:`True`, if the bot allows
|
||||
users to create and delete topics in private chats. Returned only in
|
||||
:meth:`telegram.Bot.get_me`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
.. |user_chat_id_note| replace:: This shortcuts build on the assumption that :attr:`User.id`
|
||||
coincides with the :attr:`Chat.id` of the private chat with the user. This has been the
|
||||
@@ -161,6 +172,7 @@ class User(TelegramObject):
|
||||
|
||||
__slots__ = (
|
||||
"added_to_attachment_menu",
|
||||
"allows_users_to_create_topics",
|
||||
"can_connect_to_business",
|
||||
"can_join_groups",
|
||||
"can_read_all_group_messages",
|
||||
@@ -192,6 +204,7 @@ class User(TelegramObject):
|
||||
can_connect_to_business: bool | None = None,
|
||||
has_main_web_app: bool | None = None,
|
||||
has_topics_enabled: bool | None = None,
|
||||
allows_users_to_create_topics: bool | None = None,
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
):
|
||||
@@ -212,6 +225,7 @@ class User(TelegramObject):
|
||||
self.can_connect_to_business: bool | None = can_connect_to_business
|
||||
self.has_main_web_app: bool | None = has_main_web_app
|
||||
self.has_topics_enabled: bool | None = has_topics_enabled
|
||||
self.allows_users_to_create_topics: bool | None = allows_users_to_create_topics
|
||||
|
||||
self._id_attrs = (self.id,)
|
||||
|
||||
@@ -2628,3 +2642,73 @@ class User(TelegramObject):
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
async def get_profile_audios(
|
||||
self,
|
||||
offset: int | None = None,
|
||||
limit: int | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> "UserProfileAudios":
|
||||
"""Shortcut for::
|
||||
|
||||
await bot.get_user_profile_audios(update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.get_user_profile_audios`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Returns:
|
||||
:class:`telegram.UserProfileAudios`
|
||||
|
||||
"""
|
||||
return await self.get_bot().get_user_profile_audios(
|
||||
user_id=self.id,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
async def set_chat_member_tag(
|
||||
self,
|
||||
chat_id: int | str,
|
||||
tag: str | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Shortcut for::
|
||||
|
||||
await bot.set_chat_member_tag(user_id=update.effective_user.id, *args, **kwargs)
|
||||
|
||||
For the documentation of the arguments, please see
|
||||
:meth:`telegram.Bot.set_chat_member_tag`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Returns:
|
||||
:obj:`bool`: On success, :obj:`True` is returned.
|
||||
"""
|
||||
return await self.get_bot().set_chat_member_tag(
|
||||
user_id=self.id,
|
||||
chat_id=chat_id,
|
||||
tag=tag,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=api_kwargs,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2026
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
"""This module contains an object that represents a Telegram UserProfileAudios."""
|
||||
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from telegram._files.audio import Audio
|
||||
from telegram._telegramobject import TelegramObject
|
||||
from telegram._utils.types import JSONDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from telegram import Bot
|
||||
|
||||
|
||||
class UserProfileAudios(TelegramObject):
|
||||
"""
|
||||
This object represents the audios displayed on a user's profile.
|
||||
|
||||
Objects of this class are comparable in terms of equality. Two objects of this class are
|
||||
considered equal, if their :attr:`total_count` and :attr:`audios` are equal.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
|
||||
Args:
|
||||
total_count (:obj:`int`): Total number of profile audios for the target user.
|
||||
audios (Sequence[:class:`telegram.Audio`]): Requested profile audios.
|
||||
|
||||
Attributes:
|
||||
total_count (:obj:`int`): Total number of profile audios for the target user.
|
||||
audios (tuple[:class:`telegram.Audio`]): Requested profile audios.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ("audios", "total_count")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
total_count: int,
|
||||
audios: Sequence[Audio],
|
||||
*,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
) -> None:
|
||||
super().__init__(api_kwargs=api_kwargs)
|
||||
# Required
|
||||
self.total_count: int = total_count
|
||||
self.audios: tuple[Audio, ...] = tuple(audios)
|
||||
|
||||
self._id_attrs = (self.total_count, self.audios)
|
||||
|
||||
self._freeze()
|
||||
|
||||
@classmethod
|
||||
def de_json(cls, data: JSONDict, bot: "Bot | None" = None) -> "UserProfileAudios":
|
||||
"""See :meth:`telegram.TelegramObject.de_json`."""
|
||||
data = cls._parse_data(data)
|
||||
|
||||
data["audios"] = Audio.de_list(data.get("audios", []), bot)
|
||||
|
||||
return super().de_json(data=data, bot=bot)
|
||||
@@ -51,6 +51,6 @@ class Version(NamedTuple):
|
||||
|
||||
|
||||
__version_info__: Final[Version] = Version(
|
||||
major=22, minor=6, micro=0, releaselevel="final", serial=0
|
||||
major=22, minor=7, micro=0, releaselevel="final", serial=0
|
||||
)
|
||||
__version__: Final[str] = str(__version_info__)
|
||||
|
||||
+185
-6
@@ -85,11 +85,13 @@ __all__ = [
|
||||
"InputStoryContentType",
|
||||
"InvoiceLimit",
|
||||
"KeyboardButtonRequestUsersLimit",
|
||||
"KeyboardButtonStyle",
|
||||
"LocationLimit",
|
||||
"MaskPosition",
|
||||
"MediaGroupLimit",
|
||||
"MenuButtonType",
|
||||
"MessageAttachmentType",
|
||||
"MessageEntityDateTimeFormats",
|
||||
"MessageEntityType",
|
||||
"MessageLimit",
|
||||
"MessageOriginType",
|
||||
@@ -120,10 +122,13 @@ __all__ = [
|
||||
"SuggestedPost",
|
||||
"SuggestedPostInfoState",
|
||||
"SuggestedPostRefunded",
|
||||
"TagLimit",
|
||||
"TransactionPartnerType",
|
||||
"TransactionPartnerUser",
|
||||
"UniqueGiftInfoOrigin",
|
||||
"UniqueGiftModelRarity",
|
||||
"UpdateType",
|
||||
"UserProfileAudiosLimit",
|
||||
"UserProfilePhotosLimit",
|
||||
"VerifyLimit",
|
||||
"WebhookLimit",
|
||||
@@ -176,7 +181,7 @@ class _AccentColor(NamedTuple):
|
||||
#: :data:`telegram.__bot_api_version_info__`.
|
||||
#:
|
||||
#: .. versionadded:: 20.0
|
||||
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=3)
|
||||
BOT_API_VERSION_INFO: Final[_BotAPIVersion] = _BotAPIVersion(major=9, minor=5)
|
||||
#: :obj:`str`: Telegram Bot API
|
||||
#: version supported by this version of `python-telegram-bot`. Also available as
|
||||
#: :data:`telegram.__bot_api_version__`.
|
||||
@@ -1402,6 +1407,53 @@ class InlineKeyboardButtonLimit(IntEnum):
|
||||
"""
|
||||
|
||||
|
||||
class KeyboardButtonStyle(StringEnum):
|
||||
"""This enum contains the available button styles for
|
||||
:class:`telegram.InlineKeyboardButton` and :class:`telegram.KeyboardButton`.
|
||||
The enum members of this enumeration are instances of :class:`str` and can be treated as such.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
PRIMARY = "primary"
|
||||
""":obj:`str`: Primary button style (usually blue) for the
|
||||
:paramref:`~telegram.InlineKeyboardButton.style` and
|
||||
:paramref:`~telegram.KeyboardButton.style` parameters.
|
||||
"""
|
||||
|
||||
SUCCESS = "success"
|
||||
""":obj:`str`: Success button style (usually green) for the
|
||||
:paramref:`~telegram.InlineKeyboardButton.style` and
|
||||
:paramref:`~telegram.KeyboardButton.style` parameters.
|
||||
"""
|
||||
|
||||
DANGER = "danger"
|
||||
""":obj:`str`: Danger/destructive button style (usually red) for the
|
||||
:paramref:`~telegram.InlineKeyboardButton.style` and
|
||||
:paramref:`~telegram.KeyboardButton.style` parameters.
|
||||
"""
|
||||
|
||||
BLUE = "primary"
|
||||
""":obj:`str`: Alias for :attr:`PRIMARY`. Blue button style for the
|
||||
:paramref:`~telegram.InlineKeyboardButton.style` and
|
||||
:paramref:`~telegram.KeyboardButton.style` parameters.
|
||||
"""
|
||||
|
||||
GREEN = "success"
|
||||
""":obj:`str`: Alias for :attr:`SUCCESS`. Green button style for the
|
||||
:paramref:`~telegram.InlineKeyboardButton.style` and
|
||||
:paramref:`~telegram.KeyboardButton.style` parameters.
|
||||
"""
|
||||
|
||||
RED = "danger"
|
||||
""":obj:`str`: Alias for :attr:`DANGER`. Red button style for the
|
||||
:paramref:`~telegram.InlineKeyboardButton.style` and
|
||||
:paramref:`~telegram.KeyboardButton.style` parameters.
|
||||
"""
|
||||
|
||||
|
||||
class InlineKeyboardMarkupLimit(IntEnum):
|
||||
"""This enum contains limitations for :class:`telegram.InlineKeyboardMarkup`/
|
||||
:meth:`telegram.Bot.send_message` & friends. The enum
|
||||
@@ -1967,6 +2019,11 @@ class MessageEntityType(StringEnum):
|
||||
|
||||
.. versionadded:: 20.0
|
||||
"""
|
||||
DATE_TIME = "date_time"
|
||||
""":obj:`str`: Message entities representing formatted date and time.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
EMAIL = "email"
|
||||
""":obj:`str`: Message entities representing a email."""
|
||||
EXPANDABLE_BLOCKQUOTE = "expandable_blockquote"
|
||||
@@ -1998,6 +2055,63 @@ class MessageEntityType(StringEnum):
|
||||
""":obj:`str`: Message entities representing a url."""
|
||||
|
||||
|
||||
class MessageEntityDateTimeFormats(StringEnum):
|
||||
"""This enum contains all possible formats for :attr:`telegram.MessageEntity.date_time_format`.
|
||||
Please read `date-time entity formatting
|
||||
<https://core.telegram.org/bots/api#date-time-entity-formatting>`_ for more details. The enum
|
||||
members of this enumeration are instances of :class:`str` and can be treated as such.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
RELATIVE = "r"
|
||||
""":obj:`str`: Displays the time relative to the current time."""
|
||||
LOCALIZED_WEEKDAY = "w"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language."""
|
||||
SHORT_DATE = "d"
|
||||
""":obj:`str`: Displays the date in short form (e.g., ``17.03.22``)."""
|
||||
LONG_DATE = "D"
|
||||
""":obj:`str`: Displays the date in long form (e.g., ``March 17, 2022``)."""
|
||||
SHORT_TIME = "t"
|
||||
""":obj:`str`: Displays the time in short form (e.g., ``22:45``)."""
|
||||
LONG_TIME = "T"
|
||||
""":obj:`str`: Displays the time in long form (e.g., ``22:45:00``)."""
|
||||
LOCALIZED_WEEKDAY_SHORT_DATE = "wd"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language and the date in
|
||||
short form."""
|
||||
LOCALIZED_WEEKDAY_LONG_DATE = "wD"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language and the date in
|
||||
long form."""
|
||||
LOCALIZED_WEEKDAY_SHORT_TIME = "wt"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language and the time in
|
||||
short form."""
|
||||
LOCALIZED_WEEKDAY_LONG_TIME = "wT"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language and the time in
|
||||
long form."""
|
||||
LOCALIZED_WEEKDAY_SHORT_DATE_SHORT_TIME = "wdt"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
|
||||
short form and the time in short form."""
|
||||
LOCALIZED_WEEKDAY_SHORT_DATE_LONG_TIME = "wdT"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
|
||||
short form and the time in long form."""
|
||||
LOCALIZED_WEEKDAY_LONG_DATE_SHORT_TIME = "wDt"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
|
||||
long form and the time in short form."""
|
||||
LOCALIZED_WEEKDAY_LONG_DATE_LONG_TIME = "wDT"
|
||||
""":obj:`str`: Displays the day of the week in the user's localized language, the date in
|
||||
long form and the time in long form."""
|
||||
SHORT_DATE_SHORT_TIME = "dt"
|
||||
""":obj:`str`: Displays the date in short form and the time in short form."""
|
||||
SHORT_DATE_LONG_TIME = "dT"
|
||||
""":obj:`str`: Displays the date in short form and the time in long form."""
|
||||
LONG_DATE_SHORT_TIME = "Dt"
|
||||
""":obj:`str`: Displays the date in long form and the time in short form."""
|
||||
LONG_DATE_LONG_TIME = "DT"
|
||||
""":obj:`str`: Displays the date in long form and the time in long form."""
|
||||
|
||||
|
||||
class MessageLimit(IntEnum):
|
||||
"""This enum contains limitations for :class:`telegram.Message`/
|
||||
:class:`telegram.InputTextMessageContent`/
|
||||
@@ -2109,16 +2223,26 @@ class MessageType(StringEnum):
|
||||
"""
|
||||
CHANNEL_CHAT_CREATED = "channel_chat_created"
|
||||
""":obj:`str`: Messages with :attr:`telegram.Message.channel_chat_created`."""
|
||||
CHAT_SHARED = "chat_shared"
|
||||
""":obj:`str`: Messages with :attr:`telegram.Message.chat_shared`.
|
||||
|
||||
.. versionadded:: 20.8
|
||||
"""
|
||||
CHAT_BACKGROUND_SET = "chat_background_set"
|
||||
""":obj:`str`: Messages with :attr:`telegram.Message.chat_background_set`.
|
||||
|
||||
.. versionadded:: 21.2
|
||||
"""
|
||||
CHAT_OWNER_CHANGED = "chat_owner_changed"
|
||||
""":obj:`str`: Messages with :attr:`telegram.Message.chat_owner_changed`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
CHAT_OWNER_LEFT = "chat_owner_left"
|
||||
""":obj:`str`: Messages with :attr:`telegram.Message.chat_owner_left`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
CHAT_SHARED = "chat_shared"
|
||||
""":obj:`str`: Messages with :attr:`telegram.Message.chat_shared`.
|
||||
|
||||
.. versionadded:: 20.8
|
||||
"""
|
||||
CHECKLIST = "checklist"
|
||||
""":obj:`str`: Messages with :attr:`telegram.Message.checklist`.
|
||||
|
||||
@@ -3369,6 +3493,25 @@ class UniqueGiftInfoOrigin(StringEnum):
|
||||
""":obj:`str` gift upgraded"""
|
||||
|
||||
|
||||
class UniqueGiftModelRarity(StringEnum):
|
||||
"""This enum contains the available rarities for :class:`telegram.UniqueGiftModel`. The enum
|
||||
members of this enumeration are instances of :class:`str` and can be treated as such.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
UNCOMMON = "uncommon"
|
||||
""":obj:`str` uncommon rarity"""
|
||||
RARE = "rare"
|
||||
""":obj:`str` rare rarity"""
|
||||
EPIC = "epic"
|
||||
""":obj:`str` epic rarity"""
|
||||
LEGENDARY = "legendary"
|
||||
""":obj:`str` legendary rarity"""
|
||||
|
||||
|
||||
class UpdateType(StringEnum):
|
||||
"""This enum contains the available types of :class:`telegram.Update`. The enum
|
||||
members of this enumeration are instances of :class:`str` and can be treated as such.
|
||||
@@ -3589,6 +3732,27 @@ class UserProfilePhotosLimit(IntEnum):
|
||||
"""
|
||||
|
||||
|
||||
class UserProfileAudiosLimit(IntEnum):
|
||||
"""This enum contains limitations for :paramref:`telegram.Bot.get_user_profile_audios.limit`.
|
||||
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
MIN_LIMIT = 1
|
||||
""":obj:`int`: Minimum value allowed for
|
||||
:paramref:`~telegram.Bot.get_user_profile_audios.limit` parameter of
|
||||
:meth:`telegram.Bot.get_user_profile_audios`.
|
||||
"""
|
||||
MAX_LIMIT = 100
|
||||
""":obj:`int`: Maximum value allowed for
|
||||
:paramref:`~telegram.Bot.get_user_profile_audios.limit` parameter of
|
||||
:meth:`telegram.Bot.get_user_profile_audios`.
|
||||
"""
|
||||
|
||||
|
||||
class WebhookLimit(IntEnum):
|
||||
"""This enum contains limitations for :paramref:`telegram.Bot.set_webhook.max_connections` and
|
||||
:paramref:`telegram.Bot.set_webhook.secret_token`. The enum members of this enumeration are
|
||||
@@ -3864,3 +4028,18 @@ class VerifyLimit(IntEnum):
|
||||
:paramref:`~telegram.Bot.verify_chat.custom_description` or
|
||||
:paramref:`~telegram.Bot.verify_user.custom_description` parameter.
|
||||
"""
|
||||
|
||||
|
||||
class TagLimit(IntEnum):
|
||||
"""This enum contains limitations for :meth:`~telegram.Bot.set_chat_member_tag`.
|
||||
The enum members of this enumeration are instances of :class:`int` and can be treated as such.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
MAX_TAG_LENGTH = 16
|
||||
""":obj:`int`: Maximum number of characters in a :obj:`str` passed as the
|
||||
:paramref:`~telegram.Bot.set_chat_member_tag.tag` parameter.
|
||||
"""
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
import datetime as dtm
|
||||
import time
|
||||
from collections.abc import MutableMapping
|
||||
from copy import copy
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
from uuid import uuid4
|
||||
|
||||
@@ -34,7 +35,7 @@ except ImportError:
|
||||
|
||||
import contextlib
|
||||
|
||||
from telegram import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, User
|
||||
from telegram import CallbackQuery, InlineKeyboardMarkup, Message, User
|
||||
from telegram._utils.datetime import to_float_timestamp
|
||||
from telegram.error import TelegramError
|
||||
from telegram.ext._utils.types import CDCData
|
||||
@@ -235,22 +236,22 @@ class CallbackDataCache:
|
||||
keyboard_data = _KeyboardData(keyboard_uuid)
|
||||
|
||||
# Built a new nested list of buttons by replacing the callback data if needed
|
||||
buttons = [
|
||||
[
|
||||
(
|
||||
buttons = []
|
||||
|
||||
for column in reply_markup.inline_keyboard:
|
||||
cols = []
|
||||
for btn in column:
|
||||
if btn.callback_data is not None:
|
||||
# We create a new button instead of replacing callback_data in case the
|
||||
# same object is used elsewhere
|
||||
InlineKeyboardButton(
|
||||
btn.text,
|
||||
callback_data=self.__put_button(btn.callback_data, keyboard_data),
|
||||
btn_copy = copy(btn)
|
||||
btn_copy.update_callback_data(
|
||||
self.__put_button(btn.callback_data, keyboard_data)
|
||||
)
|
||||
if btn.callback_data
|
||||
else btn
|
||||
)
|
||||
for btn in column
|
||||
]
|
||||
for column in reply_markup.inline_keyboard
|
||||
]
|
||||
cols.append(btn_copy)
|
||||
else:
|
||||
cols.append(btn)
|
||||
buttons.append(cols)
|
||||
|
||||
if not keyboard_data.button_data:
|
||||
# If we arrive here, no data had to be replaced and we can return the input
|
||||
|
||||
@@ -84,6 +84,7 @@ from telegram import (
|
||||
Update,
|
||||
User,
|
||||
UserChatBoosts,
|
||||
UserProfileAudios,
|
||||
UserProfilePhotos,
|
||||
Video,
|
||||
VideoNote,
|
||||
@@ -2366,6 +2367,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||
can_edit_stories: bool | None = None,
|
||||
can_delete_stories: bool | None = None,
|
||||
can_manage_direct_messages: bool | None = None,
|
||||
can_manage_tags: bool | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
@@ -2393,6 +2395,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||
can_edit_stories=can_edit_stories,
|
||||
can_delete_stories=can_delete_stories,
|
||||
can_manage_direct_messages=can_manage_direct_messages,
|
||||
can_manage_tags=can_manage_tags,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
@@ -4492,7 +4495,6 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||
exclude_unsaved: bool | None = None,
|
||||
exclude_saved: bool | None = None,
|
||||
exclude_unlimited: bool | None = None,
|
||||
exclude_limited: bool | None = None,
|
||||
exclude_unique: bool | None = None,
|
||||
sort_by_price: bool | None = None,
|
||||
offset: str | None = None,
|
||||
@@ -4513,7 +4515,6 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||
exclude_unsaved=exclude_unsaved,
|
||||
exclude_saved=exclude_saved,
|
||||
exclude_unlimited=exclude_unlimited,
|
||||
exclude_limited=exclude_limited,
|
||||
exclude_limited_upgradable=exclude_limited_upgradable,
|
||||
exclude_limited_non_upgradable=exclude_limited_non_upgradable,
|
||||
exclude_unique=exclude_unique,
|
||||
@@ -5424,6 +5425,92 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
|
||||
)
|
||||
|
||||
async def set_my_profile_photo(
|
||||
self,
|
||||
photo: "InputProfilePhoto",
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
rate_limit_args: RLARGS | None = None,
|
||||
) -> bool:
|
||||
return await super().set_my_profile_photo(
|
||||
photo=photo,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
|
||||
)
|
||||
|
||||
async def remove_my_profile_photo(
|
||||
self,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
rate_limit_args: RLARGS | None = None,
|
||||
) -> bool:
|
||||
return await super().remove_my_profile_photo(
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
|
||||
)
|
||||
|
||||
async def get_user_profile_audios(
|
||||
self,
|
||||
user_id: int,
|
||||
offset: int | None = None,
|
||||
limit: int | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
rate_limit_args: RLARGS | None = None,
|
||||
) -> UserProfileAudios:
|
||||
return await super().get_user_profile_audios(
|
||||
user_id=user_id,
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
|
||||
)
|
||||
|
||||
async def set_chat_member_tag(
|
||||
self,
|
||||
chat_id: int | str,
|
||||
user_id: int,
|
||||
tag: str | None = None,
|
||||
*,
|
||||
read_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
write_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
connect_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
pool_timeout: ODVInput[float] = DEFAULT_NONE,
|
||||
api_kwargs: JSONDict | None = None,
|
||||
rate_limit_args: RLARGS | None = None,
|
||||
) -> bool:
|
||||
return await super().set_chat_member_tag(
|
||||
chat_id=chat_id,
|
||||
user_id=user_id,
|
||||
tag=tag,
|
||||
read_timeout=read_timeout,
|
||||
write_timeout=write_timeout,
|
||||
connect_timeout=connect_timeout,
|
||||
pool_timeout=pool_timeout,
|
||||
api_kwargs=self._merge_api_rl_kwargs(api_kwargs, rate_limit_args),
|
||||
)
|
||||
|
||||
# updated camelCase aliases
|
||||
getMe = get_me
|
||||
sendMessage = send_message
|
||||
@@ -5586,3 +5673,7 @@ class ExtBot(Bot, Generic[RLARGS]):
|
||||
repostStory = repost_story
|
||||
getUserGifts = get_user_gifts
|
||||
getChatGifts = get_chat_gifts
|
||||
setMyProfilePhoto = set_my_profile_photo
|
||||
removeMyProfilePhoto = remove_my_profile_photo
|
||||
getUserProfileAudios = get_user_profile_audios
|
||||
setChatMemberTag = set_chat_member_tag
|
||||
|
||||
@@ -1965,6 +1965,8 @@ class StatusUpdate:
|
||||
# keep this alphabetically sorted for easier maintenance
|
||||
StatusUpdate.CHAT_BACKGROUND_SET.check_update(update)
|
||||
or StatusUpdate.CHAT_CREATED.check_update(update)
|
||||
or StatusUpdate.CHAT_OWNER_CHANGED.check_update(update)
|
||||
or StatusUpdate.CHAT_OWNER_LEFT.check_update(update)
|
||||
or StatusUpdate.CHAT_SHARED.check_update(update)
|
||||
or StatusUpdate.CHECKLIST_TASKS_ADDED.check_update(update)
|
||||
or StatusUpdate.CHECKLIST_TASKS_DONE.check_update(update)
|
||||
@@ -2033,6 +2035,30 @@ class StatusUpdate:
|
||||
:attr:`telegram.Message.supergroup_chat_created` or
|
||||
:attr:`telegram.Message.channel_chat_created`."""
|
||||
|
||||
class _ChatOwnerChanged(MessageFilter):
|
||||
__slots__ = ()
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
return bool(message.chat_owner_changed)
|
||||
|
||||
CHAT_OWNER_CHANGED = _ChatOwnerChanged(name="filters.StatusUpdate.CHAT_OWNER_CHANGED")
|
||||
"""Messages that contain :attr:`telegram.Message.chat_owner_changed`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
class _ChatOwnerLeft(MessageFilter):
|
||||
__slots__ = ()
|
||||
|
||||
def filter(self, message: Message) -> bool:
|
||||
return bool(message.chat_owner_left)
|
||||
|
||||
CHAT_OWNER_LEFT = _ChatOwnerLeft(name="filters.StatusUpdate.CHAT_OWNER_LEFT")
|
||||
"""Messages that contain :attr:`telegram.Message.chat_owner_left`.
|
||||
|
||||
.. versionadded:: 22.7
|
||||
"""
|
||||
|
||||
class _ChatShared(MessageFilter):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
@@ -201,11 +201,7 @@ class RequestParameter:
|
||||
if param_value is not None:
|
||||
param_values.append(param_value)
|
||||
input_files.extend(input_file)
|
||||
return RequestParameter(
|
||||
name=key, value=param_values, input_files=input_files if input_files else None
|
||||
)
|
||||
return RequestParameter(name=key, value=param_values, input_files=input_files or None)
|
||||
|
||||
param_value, input_files = cls._value_and_input_files_from_input(value)
|
||||
return RequestParameter(
|
||||
name=key, value=param_value, input_files=input_files if input_files else None
|
||||
)
|
||||
return RequestParameter(name=key, value=param_value, input_files=input_files or None)
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
|
||||
import pytest
|
||||
|
||||
from telegram.error import BadRequest
|
||||
from tests.auxil.files import data_file
|
||||
from tests.auxil.networking import expect_bad_request
|
||||
|
||||
@@ -51,20 +50,6 @@ def animated_sticker_file():
|
||||
yield f
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def animated_sticker_set(bot):
|
||||
ss = await bot.get_sticker_set(f"animated_test_by_{bot.username}")
|
||||
if len(ss.stickers) > 100:
|
||||
try:
|
||||
for i in range(1, 50):
|
||||
await bot.delete_sticker_from_set(ss.stickers[-i].file_id)
|
||||
except BadRequest as e:
|
||||
if e.message == "Stickerset_not_modified":
|
||||
return ss
|
||||
raise Exception("stickerset is growing too large.") from None
|
||||
return ss
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
async def audio(bot, chat_id):
|
||||
with data_file("telegram.mp3").open("rb") as f, data_file("thumb.jpg").open("rb") as thumb:
|
||||
@@ -127,20 +112,6 @@ def sticker_file():
|
||||
yield file
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def sticker_set(bot):
|
||||
ss = await bot.get_sticker_set(f"test_by_{bot.username}")
|
||||
if len(ss.stickers) > 100:
|
||||
try:
|
||||
for i in range(1, 50):
|
||||
await bot.delete_sticker_from_set(ss.stickers[-i].file_id)
|
||||
except BadRequest as e:
|
||||
if e.message == "Stickerset_not_modified":
|
||||
return ss
|
||||
raise Exception("stickerset is growing too large.") from None
|
||||
return ss
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sticker_set_thumb_file():
|
||||
with data_file("sticker_set_thumb.png").open("rb") as file:
|
||||
@@ -174,17 +145,3 @@ def video_sticker_file():
|
||||
def video_sticker(bot, chat_id):
|
||||
with data_file("telegram_video_sticker.webm").open("rb") as f:
|
||||
return bot.send_sticker(chat_id, sticker=f, timeout=50).sticker
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def video_sticker_set(bot):
|
||||
ss = await bot.get_sticker_set(f"video_test_by_{bot.username}")
|
||||
if len(ss.stickers) > 100:
|
||||
try:
|
||||
for i in range(1, 50):
|
||||
await bot.delete_sticker_from_set(ss.stickers[-i].file_id)
|
||||
except BadRequest as e:
|
||||
if e.message == "Stickerset_not_modified":
|
||||
return ss
|
||||
raise Exception("stickerset is growing too large.") from None
|
||||
return ss
|
||||
|
||||
+155
-190
@@ -493,6 +493,7 @@ class StickerSetTestBase:
|
||||
name = "NOTAREALNAME"
|
||||
sticker_type = Sticker.REGULAR
|
||||
contains_masks = True
|
||||
thumbnail = PhotoSize("thumb_file_id", "thumb_file_un_id", 100, 100, False)
|
||||
|
||||
|
||||
class TestStickerSetWithoutRequest(StickerSetTestBase):
|
||||
@@ -521,7 +522,14 @@ class TestStickerSetWithoutRequest(StickerSetTestBase):
|
||||
assert sticker_set.sticker_type == self.sticker_type
|
||||
assert sticker_set.api_kwargs == {"contains_masks": self.contains_masks}
|
||||
|
||||
def test_sticker_set_to_dict(self, sticker_set):
|
||||
def test_sticker_set_to_dict(self):
|
||||
sticker_set = StickerSet(
|
||||
self.name,
|
||||
self.title,
|
||||
self.stickers,
|
||||
self.sticker_type,
|
||||
thumbnail=self.thumbnail,
|
||||
)
|
||||
sticker_set_dict = sticker_set.to_dict()
|
||||
|
||||
assert isinstance(sticker_set_dict, dict)
|
||||
@@ -752,71 +760,86 @@ class TestStickerSetWithoutRequest(StickerSetTestBase):
|
||||
assert await offline_bot.set_sticker_keywords(sticker, ["keyword"])
|
||||
|
||||
|
||||
@pytest.mark.xdist_group("stickerset")
|
||||
class TestStickerSetWithRequest:
|
||||
async def test_create_sticker_set(
|
||||
self, bot, chat_id, sticker_file, animated_sticker_file, video_sticker_file
|
||||
# In-process guard: once a worker has set up its own sticker set, skip on subsequent tests.
|
||||
_WORKER_CREATED_STICKER_SET = False
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def _create_sticker_set(
|
||||
self,
|
||||
bot,
|
||||
chat_id,
|
||||
sticker_file,
|
||||
animated_sticker_file,
|
||||
video_sticker_file,
|
||||
sticker_set_name,
|
||||
worker_id,
|
||||
):
|
||||
"""Creates the sticker set (if needed) which is required for tests. Make sure that this
|
||||
test comes before the tests that actually use the sticker sets!
|
||||
"""Creates the sticker set (if needed) which is required for all tests below. Runs exactly
|
||||
once during setup for every xdist worker, so we don't have to worry about the sticker set
|
||||
not being there for the tests that need it.
|
||||
"""
|
||||
test_by = f"test_by_{bot.username}"
|
||||
for sticker_set in [test_by, f"animated_{test_by}", f"video_{test_by}"]:
|
||||
try:
|
||||
ss = await bot.get_sticker_set(sticker_set)
|
||||
assert isinstance(ss, StickerSet)
|
||||
if self._WORKER_CREATED_STICKER_SET:
|
||||
return
|
||||
|
||||
if len(ss.stickers) > 100:
|
||||
try:
|
||||
for i in range(1, 50):
|
||||
await bot.delete_sticker_from_set(ss.stickers[-i].file_id)
|
||||
except BadRequest as e:
|
||||
if e.message != "Stickerset_not_modified":
|
||||
raise Exception("stickerset is growing too large.") from None
|
||||
except BadRequest as e:
|
||||
if not e.message == "Stickerset_invalid":
|
||||
raise e
|
||||
try:
|
||||
ss = await bot.get_sticker_set(sticker_set_name)
|
||||
assert isinstance(ss, StickerSet)
|
||||
|
||||
if sticker_set.startswith(test_by):
|
||||
s = await bot.create_new_sticker_set(
|
||||
chat_id,
|
||||
name=sticker_set,
|
||||
title="Sticker Test",
|
||||
stickers=[
|
||||
InputSticker(
|
||||
sticker_file, emoji_list=["😄"], format=StickerFormat.STATIC
|
||||
)
|
||||
],
|
||||
if len(ss.stickers) > 100:
|
||||
try:
|
||||
await asyncio.gather(
|
||||
*[bot.delete_sticker_from_set(s.file_id) for s in ss.stickers[:-50]]
|
||||
)
|
||||
assert s
|
||||
elif sticker_set.startswith("animated"):
|
||||
a = await bot.create_new_sticker_set(
|
||||
chat_id,
|
||||
name=sticker_set,
|
||||
title="Animated Test",
|
||||
stickers=[
|
||||
InputSticker(
|
||||
animated_sticker_file,
|
||||
emoji_list=["😄"],
|
||||
format=StickerFormat.ANIMATED,
|
||||
)
|
||||
],
|
||||
)
|
||||
assert a
|
||||
elif sticker_set.startswith("video"):
|
||||
v = await bot.create_new_sticker_set(
|
||||
chat_id,
|
||||
name=sticker_set,
|
||||
title="Video Test",
|
||||
stickers=[
|
||||
InputSticker(
|
||||
video_sticker_file, emoji_list=["😄"], format=StickerFormat.VIDEO
|
||||
)
|
||||
],
|
||||
)
|
||||
assert v
|
||||
except BadRequest as e:
|
||||
if e.message != "Stickerset_not_modified":
|
||||
raise Exception("stickerset is growing too large.") from None
|
||||
except BadRequest as e:
|
||||
if e.message != "Stickerset_invalid":
|
||||
raise e
|
||||
|
||||
async def test_delete_sticker_set(self, bot, chat_id, sticker_file):
|
||||
await asyncio.sleep(1) # Avoid floods
|
||||
s = await bot.create_new_sticker_set(
|
||||
chat_id,
|
||||
name=sticker_set_name,
|
||||
title="Mixed Sticker Tests",
|
||||
stickers=[
|
||||
InputSticker(
|
||||
data_file("telegram.webp").open("rb"),
|
||||
emoji_list=["😄"],
|
||||
format=StickerFormat.STATIC,
|
||||
),
|
||||
InputSticker(
|
||||
data_file("telegram_animated_sticker.tgs").open("rb"),
|
||||
emoji_list=["😄", "⭐"],
|
||||
format=StickerFormat.ANIMATED,
|
||||
),
|
||||
# works:
|
||||
InputSticker(
|
||||
data_file("telegram_video_sticker.webm").open("rb"),
|
||||
emoji_list=["😄", "🔥"],
|
||||
format=StickerFormat.VIDEO,
|
||||
),
|
||||
],
|
||||
sticker_type="regular",
|
||||
)
|
||||
assert s
|
||||
await asyncio.sleep(2) # Avoid floods
|
||||
|
||||
TestStickerSetWithRequest._WORKER_CREATED_STICKER_SET = True
|
||||
|
||||
@pytest.fixture
|
||||
def sticker_set_name(self, bot, worker_id):
|
||||
"""Returns the name of this worker's own sticker set."""
|
||||
safe_worker_id = worker_id.replace("-", "_")
|
||||
return f"{safe_worker_id}_test_by_{bot.username}"
|
||||
|
||||
@pytest.fixture
|
||||
async def worker_sticker_set(self, bot, sticker_set_name):
|
||||
"""Fetches and returns this worker's own sticker set."""
|
||||
return await bot.get_sticker_set(sticker_set_name)
|
||||
|
||||
async def test_delete_sticker_set(self, bot, chat_id, sticker_file, sticker_set_name):
|
||||
# there is currently an issue in the API where this function claims it successfully
|
||||
# creates an already deleted sticker set while it does not. This happens when calling it
|
||||
# too soon after deleting the set. This then leads to delete_sticker_set failing since the
|
||||
@@ -834,9 +857,10 @@ class TestStickerSetWithRequest:
|
||||
assert await bot.delete_sticker_set(name)
|
||||
|
||||
async def test_set_custom_emoji_sticker_set_thumbnail(
|
||||
self, bot, chat_id, animated_sticker_file
|
||||
self, bot, chat_id, animated_sticker_file, worker_id
|
||||
):
|
||||
ss_name = f"custom_emoji_set_by_{bot.username}"
|
||||
safe_worker_id = worker_id.replace("-", "_")
|
||||
ss_name = f"{safe_worker_id}_custom_emoji_set_by_{bot.username}"
|
||||
try:
|
||||
ss = await bot.get_sticker_set(ss_name)
|
||||
assert ss.sticker_type == Sticker.CUSTOM_EMOJI
|
||||
@@ -854,8 +878,15 @@ class TestStickerSetWithRequest:
|
||||
)
|
||||
assert await bot.set_custom_emoji_sticker_set_thumbnail(ss_name, "")
|
||||
|
||||
# Test add_sticker_to_set
|
||||
async def test_bot_methods_1_png(self, bot, chat_id, sticker_file):
|
||||
async def test_add_sticker_to_set(
|
||||
self,
|
||||
bot,
|
||||
chat_id,
|
||||
sticker_file,
|
||||
video_sticker_file,
|
||||
animated_sticker_file,
|
||||
sticker_set_name,
|
||||
):
|
||||
with data_file("telegram_sticker.png").open("rb") as f:
|
||||
# chat_id was hardcoded as 95205500 but it stopped working for some reason
|
||||
file = await bot.upload_sticker_file(
|
||||
@@ -864,87 +895,65 @@ class TestStickerSetWithRequest:
|
||||
assert file
|
||||
|
||||
await asyncio.sleep(1)
|
||||
tasks = asyncio.gather(
|
||||
bot.add_sticker_to_set(
|
||||
chat_id,
|
||||
f"test_by_{bot.username}",
|
||||
sticker=InputSticker(
|
||||
sticker=file.file_id, emoji_list=["😄"], format=StickerFormat.STATIC
|
||||
),
|
||||
),
|
||||
bot.add_sticker_to_set( # Also test with file input and mask
|
||||
chat_id,
|
||||
f"test_by_{bot.username}",
|
||||
sticker=InputSticker(
|
||||
sticker=sticker_file,
|
||||
emoji_list=["😄"],
|
||||
mask_position=MaskPosition(MaskPosition.EYES, -1, 1, 2),
|
||||
format=StickerFormat.STATIC,
|
||||
),
|
||||
|
||||
assert await bot.add_sticker_to_set(
|
||||
chat_id,
|
||||
sticker_set_name,
|
||||
sticker=InputSticker(
|
||||
sticker=file.file_id, emoji_list=["😄"], format=StickerFormat.STATIC
|
||||
),
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
assert await bot.add_sticker_to_set( # Also test with file input and mask
|
||||
chat_id,
|
||||
sticker_set_name,
|
||||
sticker=InputSticker(
|
||||
sticker=data_file("telegram.webp").open("rb"),
|
||||
emoji_list=["😄"],
|
||||
mask_position=MaskPosition(MaskPosition.EYES, -1, 1, 2),
|
||||
format=StickerFormat.STATIC,
|
||||
),
|
||||
)
|
||||
assert all(await tasks)
|
||||
|
||||
async def test_bot_methods_1_tgs(self, bot, chat_id):
|
||||
await asyncio.sleep(1)
|
||||
assert await bot.add_sticker_to_set(
|
||||
chat_id,
|
||||
f"animated_test_by_{bot.username}",
|
||||
sticker_set_name,
|
||||
sticker=InputSticker(
|
||||
sticker=data_file("telegram_animated_sticker.tgs").open("rb"),
|
||||
emoji_list=["😄"],
|
||||
format=StickerFormat.ANIMATED,
|
||||
),
|
||||
)
|
||||
|
||||
async def test_bot_methods_1_webm(self, bot, chat_id):
|
||||
await asyncio.sleep(1)
|
||||
with data_file("telegram_video_sticker.webm").open("rb") as f:
|
||||
assert await bot.add_sticker_to_set(
|
||||
chat_id,
|
||||
f"video_test_by_{bot.username}",
|
||||
sticker=InputSticker(sticker=f, emoji_list=["🤔"], format=StickerFormat.VIDEO),
|
||||
)
|
||||
|
||||
# Test set_sticker_position_in_set
|
||||
async def test_bot_methods_2_png(self, bot, sticker_set):
|
||||
await asyncio.sleep(1)
|
||||
file_id = sticker_set.stickers[0].file_id
|
||||
assert await bot.set_sticker_position_in_set(file_id, 1)
|
||||
|
||||
async def test_bot_methods_2_tgs(self, bot, animated_sticker_set):
|
||||
await asyncio.sleep(1)
|
||||
file_id = animated_sticker_set.stickers[0].file_id
|
||||
assert await bot.set_sticker_position_in_set(file_id, 1)
|
||||
|
||||
async def test_bot_methods_2_webm(self, bot, video_sticker_set):
|
||||
await asyncio.sleep(1)
|
||||
file_id = video_sticker_set.stickers[0].file_id
|
||||
assert await bot.set_sticker_position_in_set(file_id, 1)
|
||||
|
||||
# Test set_sticker_set_thumb
|
||||
async def test_bot_methods_3_png(self, bot, chat_id, sticker_set_thumb_file):
|
||||
await asyncio.sleep(1)
|
||||
assert await bot.set_sticker_set_thumbnail(
|
||||
f"test_by_{bot.username}", chat_id, format="static", thumbnail=sticker_set_thumb_file
|
||||
assert await bot.add_sticker_to_set(
|
||||
chat_id,
|
||||
sticker_set_name,
|
||||
sticker=InputSticker(
|
||||
sticker=data_file("telegram_video_sticker.webm").open("rb"),
|
||||
emoji_list=["🤔"],
|
||||
format=StickerFormat.VIDEO,
|
||||
),
|
||||
)
|
||||
|
||||
async def test_bot_methods_3_tgs(
|
||||
self, bot, chat_id, animated_sticker_file, animated_sticker_set
|
||||
async def test_sticker_position_in_set(self, bot, worker_sticker_set):
|
||||
await asyncio.sleep(1)
|
||||
file_id = worker_sticker_set.stickers[0].file_id
|
||||
assert await bot.set_sticker_position_in_set(file_id, 1)
|
||||
|
||||
async def test_sticker_set_thumbnail(
|
||||
self, bot, chat_id, sticker_set_thumb_file, animated_sticker_file, sticker_set_name
|
||||
):
|
||||
await asyncio.sleep(1)
|
||||
animated_test = f"animated_test_by_{bot.username}"
|
||||
file_id = animated_sticker_set.stickers[-1].file_id
|
||||
tasks = asyncio.gather(
|
||||
bot.set_sticker_set_thumbnail(
|
||||
animated_test,
|
||||
chat_id,
|
||||
"animated",
|
||||
thumbnail=animated_sticker_file,
|
||||
),
|
||||
bot.set_sticker_set_thumbnail(animated_test, chat_id, "animated", thumbnail=file_id),
|
||||
assert await bot.set_sticker_set_thumbnail(
|
||||
sticker_set_name, chat_id, format="static", thumbnail=sticker_set_thumb_file
|
||||
)
|
||||
await asyncio.sleep(1)
|
||||
assert await bot.set_sticker_set_thumbnail(
|
||||
sticker_set_name,
|
||||
chat_id,
|
||||
"animated",
|
||||
thumbnail=animated_sticker_file,
|
||||
)
|
||||
assert all(await tasks)
|
||||
|
||||
# TODO: Try the below by creating a custom .webm and not by downloading another pack's thumb
|
||||
@pytest.mark.skip(
|
||||
@@ -954,85 +963,41 @@ class TestStickerSetWithRequest:
|
||||
def test_bot_methods_3_webm(self, bot, chat_id, video_sticker_file, video_sticker_set):
|
||||
pass
|
||||
|
||||
# Test delete_sticker_from_set
|
||||
async def test_bot_methods_4_png(self, bot, sticker_set):
|
||||
if len(sticker_set.stickers) <= 1:
|
||||
async def test_delete_sticker_from_set(self, bot, worker_sticker_set):
|
||||
if len(worker_sticker_set.stickers) <= 1:
|
||||
pytest.skip("Sticker set only has one sticker, deleting it will delete the set.")
|
||||
await asyncio.sleep(1)
|
||||
file_id = sticker_set.stickers[-1].file_id
|
||||
file_id = worker_sticker_set.stickers[-1].file_id
|
||||
assert await bot.delete_sticker_from_set(file_id)
|
||||
|
||||
async def test_bot_methods_4_tgs(self, bot, animated_sticker_set):
|
||||
if len(animated_sticker_set.stickers) <= 1:
|
||||
pytest.skip("Sticker set only has one sticker, deleting it will delete the set.")
|
||||
await asyncio.sleep(1)
|
||||
file_id = animated_sticker_set.stickers[-1].file_id
|
||||
assert await bot.delete_sticker_from_set(file_id)
|
||||
|
||||
async def test_bot_methods_4_webm(self, bot, video_sticker_set):
|
||||
if len(video_sticker_set.stickers) <= 1:
|
||||
pytest.skip("Sticker set only has one sticker, deleting it will delete the set.")
|
||||
await asyncio.sleep(1)
|
||||
file_id = video_sticker_set.stickers[-1].file_id
|
||||
assert await bot.delete_sticker_from_set(file_id)
|
||||
|
||||
# Test set_sticker_emoji_list. It has been found that the first emoji in the list is the one
|
||||
# It has been found that the first emoji in the list is the one
|
||||
# that is used in `Sticker.emoji` as string (which is returned in `get_sticker_set`)
|
||||
async def test_bot_methods_5_png(self, bot, sticker_set):
|
||||
file_id = sticker_set.stickers[-1].file_id
|
||||
async def test_set_sticker_emoji_list(self, bot, worker_sticker_set, sticker_set_name):
|
||||
file_id = worker_sticker_set.stickers[-1].file_id
|
||||
assert await bot.set_sticker_emoji_list(file_id, ["😔", "😟"])
|
||||
ss = await bot.get_sticker_set(f"test_by_{bot.username}")
|
||||
ss = await bot.get_sticker_set(sticker_set_name)
|
||||
assert ss.stickers[-1].emoji == "😔"
|
||||
|
||||
async def test_bot_methods_5_tgs(self, bot, animated_sticker_set):
|
||||
file_id = animated_sticker_set.stickers[-1].file_id
|
||||
assert await bot.set_sticker_emoji_list(file_id, ["😔", "😟"])
|
||||
ss = await bot.get_sticker_set(f"animated_test_by_{bot.username}")
|
||||
assert ss.stickers[-1].emoji == "😔"
|
||||
|
||||
async def test_bot_methods_5_webm(self, bot, video_sticker_set):
|
||||
file_id = video_sticker_set.stickers[-1].file_id
|
||||
assert await bot.set_sticker_emoji_list(file_id, ["😔", "😟"])
|
||||
ss = await bot.get_sticker_set(f"video_test_by_{bot.username}")
|
||||
assert ss.stickers[-1].emoji == "😔"
|
||||
|
||||
# Test set_sticker_set_title.
|
||||
async def test_bot_methods_6_png(self, bot):
|
||||
assert await bot.set_sticker_set_title(f"test_by_{bot.username}", "new title")
|
||||
ss = await bot.get_sticker_set(f"test_by_{bot.username}")
|
||||
async def test_sticker_set_title(self, bot, sticker_set_name):
|
||||
assert await bot.set_sticker_set_title(sticker_set_name, "new title")
|
||||
ss = await bot.get_sticker_set(sticker_set_name)
|
||||
assert ss.title == "new title"
|
||||
|
||||
async def test_bot_methods_6_tgs(self, bot):
|
||||
assert await bot.set_sticker_set_title(f"animated_test_by_{bot.username}", "new title")
|
||||
ss = await bot.get_sticker_set(f"animated_test_by_{bot.username}")
|
||||
assert ss.title == "new title"
|
||||
|
||||
async def test_bot_methods_6_webm(self, bot):
|
||||
assert await bot.set_sticker_set_title(f"video_test_by_{bot.username}", "new title")
|
||||
ss = await bot.get_sticker_set(f"video_test_by_{bot.username}")
|
||||
assert ss.title == "new title"
|
||||
|
||||
# Test set_sticker_keywords. No way to find out the set keywords on a sticker after setting it.
|
||||
async def test_bot_methods_7_png(self, bot, sticker_set):
|
||||
file_id = sticker_set.stickers[-1].file_id
|
||||
# No way to find out the set keywords on a sticker after setting it.
|
||||
async def test_set_sticker_keywords(self, bot, worker_sticker_set):
|
||||
file_id = worker_sticker_set.stickers[-1].file_id
|
||||
assert await bot.set_sticker_keywords(file_id, ["test", "test2"])
|
||||
|
||||
async def test_bot_methods_7_tgs(self, bot, animated_sticker_set):
|
||||
file_id = animated_sticker_set.stickers[-1].file_id
|
||||
assert await bot.set_sticker_keywords(file_id, ["test", "test2"])
|
||||
|
||||
async def test_bot_methods_7_webm(self, bot, video_sticker_set):
|
||||
file_id = video_sticker_set.stickers[-1].file_id
|
||||
assert await bot.set_sticker_keywords(file_id, ["test", "test2"])
|
||||
|
||||
async def test_bot_methods_8_png(self, bot, sticker_set, sticker_file):
|
||||
file_id = sticker_set.stickers[-1].file_id
|
||||
async def test_replace_sticker_in_set(
|
||||
self, bot, worker_sticker_set, sticker_file, sticker_set_name
|
||||
):
|
||||
file_id = worker_sticker_set.stickers[-1].file_id
|
||||
assert await bot.replace_sticker_in_set(
|
||||
bot.id,
|
||||
f"test_by_{bot.username}",
|
||||
sticker_set_name,
|
||||
file_id,
|
||||
sticker=InputSticker(
|
||||
sticker=sticker_file,
|
||||
sticker=data_file("telegram.webp").open("rb"),
|
||||
emoji_list=["😄"],
|
||||
format=StickerFormat.STATIC,
|
||||
),
|
||||
|
||||
@@ -23,7 +23,16 @@ from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from telegram import Bot, InputFile, MessageEntity, PhotoSize, ReplyParameters, Video, Voice
|
||||
from telegram import (
|
||||
Bot,
|
||||
InputFile,
|
||||
MessageEntity,
|
||||
PhotoSize,
|
||||
ReplyParameters,
|
||||
Video,
|
||||
VideoQuality,
|
||||
Voice,
|
||||
)
|
||||
from telegram.constants import ParseMode
|
||||
from telegram.error import BadRequest, TelegramError
|
||||
from telegram.helpers import escape_markdown
|
||||
@@ -42,6 +51,9 @@ from tests.auxil.slots import mro_slots
|
||||
# Override `video` fixture to provide start_timestamp
|
||||
@pytest.fixture(scope="module")
|
||||
async def video(bot, chat_id):
|
||||
# The `video` object returned here does not have the `qualities` attribute.
|
||||
# Tests using actual VideoQuality objects from real Telegram responses
|
||||
# are implemented separately in `test_videoquality`.
|
||||
with data_file("telegram.mp4").open("rb") as f:
|
||||
return (
|
||||
await bot.send_video(
|
||||
@@ -60,6 +72,7 @@ class VideoTestBase:
|
||||
file_name = "telegram.mp4"
|
||||
start_timestamp = dtm.timedelta(seconds=3)
|
||||
cover = (PhotoSize("file_id", "unique_id", 640, 360, file_size=0),)
|
||||
qualities = (VideoQuality("video_file_id", "video_unique_id", 640, 360, "h264", file_size=0),)
|
||||
thumb_width = 180
|
||||
thumb_height = 320
|
||||
thumb_file_size = 1767
|
||||
@@ -109,6 +122,7 @@ class TestVideoWithoutRequest(VideoTestBase):
|
||||
"file_name": self.file_name,
|
||||
"start_timestamp": int(self.start_timestamp.total_seconds()),
|
||||
"cover": [photo_size.to_dict() for photo_size in self.cover],
|
||||
"qualities": [video_quality.to_dict() for video_quality in self.qualities],
|
||||
}
|
||||
json_video = Video.de_json(json_dict, offline_bot)
|
||||
assert json_video.api_kwargs == {}
|
||||
@@ -123,6 +137,7 @@ class TestVideoWithoutRequest(VideoTestBase):
|
||||
assert json_video.file_name == self.file_name
|
||||
assert json_video._start_timestamp == self.start_timestamp
|
||||
assert json_video.cover == self.cover
|
||||
assert json_video.qualities == self.qualities
|
||||
|
||||
def test_to_dict(self, video):
|
||||
video_dict = video.to_dict()
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python
|
||||
# A library that provides a Python interface to the Telegram Bot API
|
||||
# Copyright (C) 2015-2026
|
||||
# Leandro Toledo de Souza <devs@python-telegram-bot.org>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser Public License
|
||||
# along with this program. If not, see [http://www.gnu.org/licenses/].
|
||||
import pytest
|
||||
|
||||
from telegram import PhotoSize, VideoQuality
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def video_quality_message_id():
|
||||
# A video message with exactly one available video quality:
|
||||
# https://t.me/pythontelegrambottests/375821
|
||||
# See discussion: https://t.me/pythontelegrambotdev/1782
|
||||
return 375821
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
async def video_quality_list(bot, chat_id, channel_id, video_quality_message_id):
|
||||
return (
|
||||
await bot.forward_message(chat_id, channel_id, video_quality_message_id, read_timeout=50)
|
||||
).video.qualities
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def video_quality(video_quality_list):
|
||||
return video_quality_list[-1]
|
||||
|
||||
|
||||
class VideoQualityTestBase:
|
||||
# These values are tied to the forwarded video and must remain unchanged
|
||||
width = 464
|
||||
height = 848
|
||||
codec = "h264"
|
||||
file_size = 405540
|
||||
|
||||
|
||||
class TestVideoQualityWithoutRequest(VideoQualityTestBase):
|
||||
def test_qualities_available(self, video_quality_list):
|
||||
assert isinstance(video_quality_list, tuple)
|
||||
# Subsequent tests relie on the forwarded video
|
||||
# having exactly one video quality.
|
||||
assert len(video_quality_list) == 1
|
||||
|
||||
def test_slot_behaviour(self, video_quality):
|
||||
for attr in video_quality.__slots__:
|
||||
assert getattr(video_quality, attr, "err") != "err", f"got extra slot '{attr}'"
|
||||
assert len(mro_slots(video_quality)) == len(set(mro_slots(video_quality))), (
|
||||
"duplicate slot"
|
||||
)
|
||||
|
||||
def test_creation(self, video_quality):
|
||||
assert isinstance(video_quality, VideoQuality)
|
||||
assert isinstance(video_quality.file_id, str)
|
||||
assert isinstance(video_quality.file_unique_id, str)
|
||||
assert video_quality.file_id
|
||||
assert video_quality.file_unique_id
|
||||
|
||||
def test_expected_values(self, video_quality):
|
||||
assert video_quality.width == self.width
|
||||
assert video_quality.height == self.height
|
||||
assert video_quality.codec == self.codec
|
||||
assert video_quality.file_size == self.file_size
|
||||
|
||||
def test_de_json(self, offline_bot, video_quality):
|
||||
json_dict = {
|
||||
"file_id": video_quality.file_id,
|
||||
"file_unique_id": video_quality.file_unique_id,
|
||||
"width": self.width,
|
||||
"height": self.height,
|
||||
"codec": self.codec,
|
||||
"file_size": self.file_size,
|
||||
}
|
||||
json_videoquality = VideoQuality.de_json(json_dict, offline_bot)
|
||||
assert json_videoquality.api_kwargs == {}
|
||||
|
||||
assert json_videoquality.file_id == video_quality.file_id
|
||||
assert json_videoquality.file_unique_id == video_quality.file_unique_id
|
||||
assert json_videoquality.width == self.width
|
||||
assert json_videoquality.height == self.height
|
||||
assert json_videoquality.codec == self.codec
|
||||
assert json_videoquality.file_size == self.file_size
|
||||
|
||||
def test_to_dict(self, video_quality):
|
||||
videoquality_dict = video_quality.to_dict()
|
||||
|
||||
assert isinstance(videoquality_dict, dict)
|
||||
assert videoquality_dict["file_id"] == video_quality.file_id
|
||||
assert videoquality_dict["file_unique_id"] == video_quality.file_unique_id
|
||||
assert videoquality_dict["width"] == video_quality.width
|
||||
assert videoquality_dict["height"] == video_quality.height
|
||||
assert videoquality_dict["codec"] == video_quality.codec
|
||||
assert videoquality_dict["file_size"] == video_quality.file_size
|
||||
|
||||
def test_equality(self, video_quality):
|
||||
a = VideoQuality(
|
||||
video_quality.file_id,
|
||||
video_quality.file_unique_id,
|
||||
self.width,
|
||||
self.height,
|
||||
self.codec,
|
||||
)
|
||||
b = VideoQuality("", video_quality.file_unique_id, self.width, self.height, self.codec)
|
||||
c = VideoQuality(video_quality.file_id, video_quality.file_unique_id, 0, 0, self.codec)
|
||||
d = VideoQuality("", "", self.width, self.height, self.codec)
|
||||
e = PhotoSize(
|
||||
video_quality.file_id,
|
||||
video_quality.file_unique_id,
|
||||
self.width,
|
||||
self.height,
|
||||
)
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
assert a is not b
|
||||
|
||||
assert a == c
|
||||
assert hash(a) == hash(c)
|
||||
|
||||
assert a != d
|
||||
assert hash(a) != hash(d)
|
||||
|
||||
assert a != e
|
||||
assert hash(a) != hash(e)
|
||||
|
||||
|
||||
class TestVideoWithRequest(VideoQualityTestBase):
|
||||
async def test_get_and_download(self, bot, video_quality, tmp_file):
|
||||
new_file = await bot.get_file(video_quality.file_id)
|
||||
|
||||
assert new_file.file_size == video_quality.file_size
|
||||
assert new_file.file_unique_id == video_quality.file_unique_id
|
||||
assert new_file.file_path.startswith("https://")
|
||||
|
||||
await new_file.download_to_drive(tmp_file)
|
||||
|
||||
assert tmp_file.is_file()
|
||||
|
||||
async def test_resend(self, bot, chat_id, video_quality):
|
||||
message = await bot.send_video(chat_id, video_quality.file_id)
|
||||
|
||||
assert message.video.file_id == video_quality.file_id
|
||||
assert message.video.file_unique_id == video_quality.file_unique_id
|
||||
@@ -48,6 +48,8 @@ def inline_keyboard_button():
|
||||
InlineKeyboardButtonTestBase.switch_inline_query_chosen_chat
|
||||
),
|
||||
copy_text=InlineKeyboardButtonTestBase.copy_text,
|
||||
style=InlineKeyboardButtonTestBase.style,
|
||||
icon_custom_emoji_id=InlineKeyboardButtonTestBase.icon_custom_emoji_id,
|
||||
)
|
||||
|
||||
|
||||
@@ -63,6 +65,8 @@ class InlineKeyboardButtonTestBase:
|
||||
web_app = WebAppInfo(url="https://example.com")
|
||||
switch_inline_query_chosen_chat = SwitchInlineQueryChosenChat("a_bot", True, False, True, True)
|
||||
copy_text = CopyTextButton("python-telegram-bot")
|
||||
style = "danger"
|
||||
icon_custom_emoji_id = "5237829955978547322"
|
||||
|
||||
|
||||
class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
|
||||
@@ -90,6 +94,8 @@ class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
|
||||
== self.switch_inline_query_chosen_chat
|
||||
)
|
||||
assert inline_keyboard_button.copy_text == self.copy_text
|
||||
assert inline_keyboard_button.style == self.style
|
||||
assert inline_keyboard_button.icon_custom_emoji_id == self.icon_custom_emoji_id
|
||||
|
||||
def test_to_dict(self, inline_keyboard_button):
|
||||
inline_keyboard_button_dict = inline_keyboard_button.to_dict()
|
||||
@@ -122,6 +128,11 @@ class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
|
||||
assert (
|
||||
inline_keyboard_button_dict["copy_text"] == inline_keyboard_button.copy_text.to_dict()
|
||||
)
|
||||
assert inline_keyboard_button_dict["style"] == inline_keyboard_button.style
|
||||
assert (
|
||||
inline_keyboard_button_dict["icon_custom_emoji_id"]
|
||||
== inline_keyboard_button.icon_custom_emoji_id
|
||||
)
|
||||
|
||||
def test_de_json(self, offline_bot):
|
||||
json_dict = {
|
||||
@@ -136,6 +147,8 @@ class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
|
||||
"pay": self.pay,
|
||||
"switch_inline_query_chosen_chat": self.switch_inline_query_chosen_chat.to_dict(),
|
||||
"copy_text": self.copy_text.to_dict(),
|
||||
"style": self.style,
|
||||
"icon_custom_emoji_id": self.icon_custom_emoji_id,
|
||||
}
|
||||
|
||||
inline_keyboard_button = InlineKeyboardButton.de_json(json_dict, None)
|
||||
@@ -158,6 +171,8 @@ class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
|
||||
== self.switch_inline_query_chosen_chat
|
||||
)
|
||||
assert inline_keyboard_button.copy_text == self.copy_text
|
||||
assert inline_keyboard_button.style == self.style
|
||||
assert inline_keyboard_button.icon_custom_emoji_id == self.icon_custom_emoji_id
|
||||
|
||||
def test_equality(self):
|
||||
a = InlineKeyboardButton("text", callback_data="data")
|
||||
@@ -167,6 +182,8 @@ class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
|
||||
e = InlineKeyboardButton("text", url="http://google.com")
|
||||
f = InlineKeyboardButton("text", web_app=WebAppInfo(url="https://ptb.org"))
|
||||
g = LoginUrl("http://google.com")
|
||||
h = InlineKeyboardButton("test", style="primary")
|
||||
i = InlineKeyboardButton("test", icon_custom_emoji_id="123")
|
||||
|
||||
assert a == b
|
||||
assert hash(a) == hash(b)
|
||||
@@ -186,6 +203,12 @@ class TestInlineKeyboardButtonWithoutRequest(InlineKeyboardButtonTestBase):
|
||||
assert a != g
|
||||
assert hash(a) != hash(g)
|
||||
|
||||
assert a != h
|
||||
assert hash(a) != hash(h)
|
||||
|
||||
assert h != i
|
||||
assert hash(h) != hash(i)
|
||||
|
||||
@pytest.mark.parametrize("callback_data", ["foo", 1, ("da", "ta"), object()])
|
||||
def test_update_callback_data(self, callback_data):
|
||||
button = InlineKeyboardButton(text="test", callback_data="data")
|
||||
|
||||
@@ -26,6 +26,7 @@ from telegram import (
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
)
|
||||
from telegram.constants import KeyboardButtonStyle
|
||||
from tests.auxil.slots import mro_slots
|
||||
|
||||
|
||||
@@ -239,3 +240,24 @@ class TestInlineKeyborardMarkupWithRequest(InlineKeyboardMarkupTestBase):
|
||||
)
|
||||
|
||||
assert message.text == "Testing InlineKeyboardMarkup"
|
||||
|
||||
async def test_send_message_with_colored_inline_keyboard_button(self, bot, chat_id):
|
||||
markup = InlineKeyboardMarkup(
|
||||
[
|
||||
[
|
||||
InlineKeyboardButton(
|
||||
text="Colored Button",
|
||||
callback_data="data1",
|
||||
style=KeyboardButtonStyle.DANGER,
|
||||
)
|
||||
]
|
||||
]
|
||||
)
|
||||
message = await bot.send_message(
|
||||
chat_id,
|
||||
"Testing colored InlineKeyboardButton",
|
||||
reply_markup=markup,
|
||||
)
|
||||
assert message.text == "Testing colored InlineKeyboardButton"
|
||||
button = message.reply_markup.inline_keyboard[0][0]
|
||||
assert button.style == "danger"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user