Compare commits

...

40 Commits

Author SHA1 Message Date
Harshil 5a41d2ba85 Bump Version to v22.7 (#5176) 2026-03-16 05:33:25 -04:00
Bibo-Joshi 108bfaf888 Documentation Improvements (#5119)
Co-authored-by: Poolitzer <github@poolitzer.eu>
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
2026-03-16 05:04:06 -04:00
renovate[bot] 3fd6932c2d Lock file maintenance (#5175)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 08:45:41 +00:00
renovate[bot] 22c31b187a Update sigstore/gh-action-sigstore-python action to v3.2.0 (#5172)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 08:27:21 +00:00
renovate[bot] dd8c60fa76 Update stefanzweifel/git-auto-commit-action action to v7.1.0 (#5173)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 08:17:34 +00:00
renovate[bot] 5bc0f4f5b4 Update actions/checkout action to v6 (#5174)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 08:08:35 +00:00
renovate[bot] 80f3ccdcfa Update GitHub Artifact Actions (major) (#5171)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 07:45:18 +00:00
renovate[bot] 537693f082 Update dependency astral-sh/uv to v0.10.10 (#5169)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 07:14:20 +00:00
renovate[bot] 72e8ded8cf Update astral-sh/setup-uv action to v7.5.0 (#5170)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 06:56:36 +00:00
Harshil a48b08fb20 Bump Sphinx Related Dependencies, Fix uv and Docs Build (#5168) 2026-03-16 02:46:13 -04:00
renovate[bot] ca2a834091 Update dependency sphinx to v9 (#5167)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-16 03:53:43 +00:00
Harshil 66e4103318 Refactor TestStickerSetWithRequest tests (#5161) 2026-03-15 23:47:09 -04:00
Poolitzer 0cceafcab3 Full support for Bot API 9.5 (#5155)
Co-authored-by: Harshil <37377066+harshil21@users.noreply.github.com>
Co-authored-by: OuYoung <212045739+ouyooung@users.noreply.github.com>
Co-authored-by: Hethon <65696516+hethon@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
2026-03-15 02:58:30 -04:00
renovate[bot] fb5234d9f5 Update Ruff to v0.15.6 (#5166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-15 01:42:49 +00:00
Harshil 6665c147d2 Preserve InlineKeyboardButton Arguments During callback_data Replacement (#5159) 2026-03-13 20:40:46 -04:00
Harshil b18e46a80d Make CI Run After chango Commits (#5157) 2026-03-12 18:09:43 -04:00
renovate[bot] f85da33619 Update dependency tornado to v6.5.5 [SECURITY] (#5164)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-12 01:24:41 +00:00
Harshil cd015737eb Replace pre-commit with prek (#5142) 2026-03-07 19:51:59 -05:00
renovate[bot] 330d2c2b99 Update Ruff to v0.15.5 (#5156)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-08 00:24:13 +00:00
Harshil 0ae9f7b6c4 Full support for Bot API 9.4 (#5137)
Co-authored-by: OuYoung <212045739+ouyooung@users.noreply.github.com>
Co-authored-by: poolitzer <github@poolitzer.eu>
Co-authored-by: Hethon <65696516+hethon@users.noreply.github.com>
Co-authored-by: Abdelrahman Elkheir <90580077+aelkheir@users.noreply.github.com>
2026-03-07 18:29:00 -05:00
renovate[bot] cb239e7b1e Update codecov/test-results-action action to v1.2.1 (#5153)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 01:02:25 +00:00
renovate[bot] cec4a6fe6f Update astral-sh/setup-uv action to v7.3.1 (#5152)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 01:02:22 +00:00
renovate[bot] 5660fd8a16 Update dependency astral-sh/uv to v0.10.7 (#5154)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 01:01:36 +00:00
renovate[bot] c19cd7b7bd Update actions/stale action to v10.2.0 (#5151)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 00:53:58 +00:00
renovate[bot] 48f8907882 Update Ruff to v0.15.4 (#5150)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-03-01 00:53:17 +00:00
Harshil f385a5e769 Remove Functionality Deprecated in Bot API 9.3 (#5143) 2026-02-24 13:29:14 -05:00
renovate[bot] f18ab3a62a Update Ruff to v0.15.2 (#5146)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-22 05:05:31 +00:00
renovate[bot] 4bf77904fb Update Pylint to v4.0.5 (#5145)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-22 00:53:52 +00:00
Poolitzer 5dec05e0c3 Fix: Moved inline test file to _inline folder (#5140) 2026-02-19 10:55:48 -05:00
renovate[bot] 3b4426fb82 Update Ruff to v0.15.1 (#5135)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-15 02:15:03 +00:00
renovate[bot] 89911bf708 Update dependency cryptography to v46.0.5 [SECURITY] (#5125)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 22:30:16 +00:00
Poolitzer 2e7ec1d8db Fixing failing sphinx builds (#5124) 2026-02-10 23:27:15 +01:00
renovate[bot] 8844fb3b2f Update Ruff to v0.15.0 (#5122)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: poolitzer <github@poolitzer.eu>
2026-02-10 22:01:22 +00:00
renovate[bot] b82b25feeb Update pre-commit hook cachetools to v7 (#5117)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-10 17:22:38 +01:00
renovate[bot] 0653b52222 Update astral-sh/setup-uv action to v7.2.1 (#5116)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 02:15:24 +00:00
renovate[bot] 3df3e6a534 Update dependency astral-sh/uv to v0.9.28 (#5113)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 02:12:57 +00:00
renovate[bot] dcf7cc4091 Update dependency pytest to v9.0.2 (#5114)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 02:10:19 +00:00
renovate[bot] c851d4360f Update actions/setup-python action to v6.2.0 (#5115)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 02:06:48 +00:00
renovate[bot] 6b42bb83d2 Update codecov/codecov-action action to v5.5.2 (#5112)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-02-01 02:06:19 +00:00
renovate[bot] dc587ade7e Update Ruff to v0.14.14 (#5110)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-01-25 00:38:39 +00:00
123 changed files with 3515 additions and 1340 deletions
+2 -2
View File
@@ -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):
+1 -1
View File
@@ -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:
-9
View File
@@ -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]"]
+3 -3
View File
@@ -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
+3 -3
View File
@@ -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
+3 -2
View File
@@ -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'
+3 -3
View File
@@ -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.*
+2 -2
View File
@@ -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:
-19
View File
@@ -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 }}"
+26
View File
@@ -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
+10 -10
View File
@@ -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
+9 -9
View File
@@ -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/
+1 -1
View File
@@ -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
+3 -2
View File
@@ -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
View File
@@ -1,6 +1,7 @@
name: Check Type Completeness
on:
pull_request:
types: [synchronize, reopened, ready_for_review]
paths:
- src/telegram/**
- pyproject.toml
+5 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 = []
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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(
+4
View File
@@ -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
+9
View File
@@ -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
+4
View File
@@ -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:
+6
View File
@@ -0,0 +1,6 @@
ChatOwnerLeft
=============
.. autoclass:: telegram.ChatOwnerLeft
:members:
:show-inheritance:
@@ -0,0 +1,6 @@
UserProfileAudios
=================
.. autoclass:: telegram.UserProfileAudios
:members:
:show-inheritance:
+8
View File
@@ -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
View File
@@ -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",
+7
View File
@@ -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
View File
@@ -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`"""
+37
View File
@@ -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.
+20 -1
View File
@@ -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()
+14
View File
@@ -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)
+44 -2
View File
@@ -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):
+113
View File
@@ -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)
+19 -4
View File
@@ -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":
+13
View File
@@ -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)
+82
View File
@@ -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
+51 -3
View File
@@ -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
+62 -7
View File
@@ -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()
+65 -7
View File
@@ -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"![{escaped_text}](tg://emoji?id={custom_emoji_id})"
elif entity.type == MessageEntity.DATE_TIME:
if entity.date_time_format:
insert = (
f"![{escaped_text}](tg://time?unix={to_timestamp(entity.unix_time)}"
f"&format={entity.date_time_format})"
)
else:
insert = f"![{escaped_text}](tg://time?unix={to_timestamp(entity.unix_time)})"
else:
insert = escaped_text
+58 -3
View File
@@ -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
View File
@@ -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`."""
+84
View File
@@ -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,
)
+76
View File
@@ -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)
+1 -1
View File
@@ -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
View File
@@ -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.
"""
+15 -14
View File
@@ -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
+93 -2
View File
@@ -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
+26
View File
@@ -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__ = ()
+2 -6
View File
@@ -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)
-43
View File
@@ -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
View File
@@ -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,
),
+16 -1
View File
@@ -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()
+157
View File
@@ -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