tdelegram 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. tdelegram-0.1.0/.env.example +8 -0
  2. tdelegram-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +96 -0
  3. tdelegram-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  4. tdelegram-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
  5. tdelegram-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +11 -0
  6. tdelegram-0.1.0/.github/workflows/ci.yml +43 -0
  7. tdelegram-0.1.0/.github/workflows/docker.yml +195 -0
  8. tdelegram-0.1.0/.github/workflows/release.yml +155 -0
  9. tdelegram-0.1.0/.gitignore +233 -0
  10. tdelegram-0.1.0/CHANGELOG.md +332 -0
  11. tdelegram-0.1.0/CLAUDE.md +224 -0
  12. tdelegram-0.1.0/CODE_OF_CONDUCT.md +137 -0
  13. tdelegram-0.1.0/CONTRIBUTING.md +22 -0
  14. tdelegram-0.1.0/Dockerfile +54 -0
  15. tdelegram-0.1.0/LICENSE +201 -0
  16. tdelegram-0.1.0/NOTICE +5 -0
  17. tdelegram-0.1.0/PKG-INFO +200 -0
  18. tdelegram-0.1.0/README.md +158 -0
  19. tdelegram-0.1.0/RELEASING.md +169 -0
  20. tdelegram-0.1.0/SECURITY.md +12 -0
  21. tdelegram-0.1.0/pyproject.toml +87 -0
  22. tdelegram-0.1.0/scripts/generate_method_registry.py +389 -0
  23. tdelegram-0.1.0/scripts/release.py +266 -0
  24. tdelegram-0.1.0/skills/tdelegram/SKILL.md +290 -0
  25. tdelegram-0.1.0/skills/tdelegram/evals/evals.json +51 -0
  26. tdelegram-0.1.0/skills/tdelegram/references/commands.md +216 -0
  27. tdelegram-0.1.0/skills/tdelegram/references/library.md +229 -0
  28. tdelegram-0.1.0/skills/tdelegram/references/recipes.md +242 -0
  29. tdelegram-0.1.0/skills/tdelegram/references/setup.md +226 -0
  30. tdelegram-0.1.0/skills/tdelegram/references/troubleshooting.md +166 -0
  31. tdelegram-0.1.0/src/tdelegram/__init__.py +42 -0
  32. tdelegram-0.1.0/src/tdelegram/api/__init__.py +47 -0
  33. tdelegram-0.1.0/src/tdelegram/api/account.py +69 -0
  34. tdelegram-0.1.0/src/tdelegram/api/admin.py +225 -0
  35. tdelegram-0.1.0/src/tdelegram/api/bots.py +134 -0
  36. tdelegram-0.1.0/src/tdelegram/api/chats.py +314 -0
  37. tdelegram-0.1.0/src/tdelegram/api/contacts.py +78 -0
  38. tdelegram-0.1.0/src/tdelegram/api/drafts.py +49 -0
  39. tdelegram-0.1.0/src/tdelegram/api/export.py +275 -0
  40. tdelegram-0.1.0/src/tdelegram/api/folders.py +77 -0
  41. tdelegram-0.1.0/src/tdelegram/api/inbox.py +124 -0
  42. tdelegram-0.1.0/src/tdelegram/api/media.py +202 -0
  43. tdelegram-0.1.0/src/tdelegram/api/messages.py +400 -0
  44. tdelegram-0.1.0/src/tdelegram/api/polls.py +51 -0
  45. tdelegram-0.1.0/src/tdelegram/api/proxies.py +222 -0
  46. tdelegram-0.1.0/src/tdelegram/api/reactions.py +43 -0
  47. tdelegram-0.1.0/src/tdelegram/api/search.py +103 -0
  48. tdelegram-0.1.0/src/tdelegram/api/secret.py +46 -0
  49. tdelegram-0.1.0/src/tdelegram/api/stories.py +42 -0
  50. tdelegram-0.1.0/src/tdelegram/api/topics.py +161 -0
  51. tdelegram-0.1.0/src/tdelegram/api/updates.py +106 -0
  52. tdelegram-0.1.0/src/tdelegram/api/users.py +82 -0
  53. tdelegram-0.1.0/src/tdelegram/auth.py +433 -0
  54. tdelegram-0.1.0/src/tdelegram/cli/__init__.py +0 -0
  55. tdelegram-0.1.0/src/tdelegram/cli/context.py +301 -0
  56. tdelegram-0.1.0/src/tdelegram/cli/main.py +1050 -0
  57. tdelegram-0.1.0/src/tdelegram/cli/output.py +107 -0
  58. tdelegram-0.1.0/src/tdelegram/client.py +198 -0
  59. tdelegram-0.1.0/src/tdelegram/config.py +113 -0
  60. tdelegram-0.1.0/src/tdelegram/credentials.py +227 -0
  61. tdelegram-0.1.0/src/tdelegram/dates.py +65 -0
  62. tdelegram-0.1.0/src/tdelegram/entities.py +91 -0
  63. tdelegram-0.1.0/src/tdelegram/errors.py +127 -0
  64. tdelegram-0.1.0/src/tdelegram/files.py +100 -0
  65. tdelegram-0.1.0/src/tdelegram/loop.py +197 -0
  66. tdelegram-0.1.0/src/tdelegram/methods.json +4090 -0
  67. tdelegram-0.1.0/src/tdelegram/normalize.py +378 -0
  68. tdelegram-0.1.0/src/tdelegram/paging.py +127 -0
  69. tdelegram-0.1.0/src/tdelegram/py.typed +0 -0
  70. tdelegram-0.1.0/src/tdelegram/safety.py +43 -0
  71. tdelegram-0.1.0/src/tdelegram/schema.json +3211 -0
  72. tdelegram-0.1.0/src/tdelegram/schema.py +233 -0
  73. tdelegram-0.1.0/src/tdelegram/tdjson.py +54 -0
  74. tdelegram-0.1.0/src/tdelegram/transport.py +196 -0
  75. tdelegram-0.1.0/tests/__init__.py +0 -0
  76. tdelegram-0.1.0/tests/conftest.py +64 -0
  77. tdelegram-0.1.0/tests/contract/__init__.py +0 -0
  78. tdelegram-0.1.0/tests/contract/test_registry.py +212 -0
  79. tdelegram-0.1.0/tests/integration/__init__.py +0 -0
  80. tdelegram-0.1.0/tests/integration/test_live.py +160 -0
  81. tdelegram-0.1.0/tests/unit/__init__.py +0 -0
  82. tdelegram-0.1.0/tests/unit/test_auth.py +286 -0
  83. tdelegram-0.1.0/tests/unit/test_cli.py +1816 -0
  84. tdelegram-0.1.0/tests/unit/test_coverage.py +1038 -0
  85. tdelegram-0.1.0/tests/unit/test_credentials.py +350 -0
  86. tdelegram-0.1.0/tests/unit/test_export.py +259 -0
  87. tdelegram-0.1.0/tests/unit/test_loop.py +136 -0
  88. tdelegram-0.1.0/tests/unit/test_normalize.py +372 -0
  89. tdelegram-0.1.0/tests/unit/test_proxies.py +91 -0
  90. tdelegram-0.1.0/tests/unit/test_release.py +274 -0
  91. tdelegram-0.1.0/tests/unit/test_safety.py +84 -0
  92. tdelegram-0.1.0/tests/unit/test_schema.py +152 -0
@@ -0,0 +1,8 @@
1
+ # Copy to .env or export directly. Never commit real secrets.
2
+ TELEGRAM_API_ID=
3
+ TELEGRAM_API_HASH=
4
+ TELEGRAM_PHONE=
5
+ # Optional:
6
+ TELEGRAM_DB_KEY=
7
+ TDELEGRAM_TDJSON=
8
+ TDELEGRAM_HOME=
@@ -0,0 +1,96 @@
1
+ name: Bug report
2
+ description: Something behaves differently than documented
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ **Before you paste anything:** TDelegram talks to a real Telegram
9
+ account. Never include your `api_hash`, your session directory
10
+ (`~/.tdelegram/`), a database key, or an unredacted log. Replace chat
11
+ titles, phone numbers and message text if they are not yours to share.
12
+
13
+ Found a security problem? Do not open an issue — see
14
+ [SECURITY.md](../blob/main/SECURITY.md).
15
+
16
+ - type: textarea
17
+ id: what-happened
18
+ attributes:
19
+ label: What happened
20
+ description: What you expected, and what you got instead.
21
+ validations:
22
+ required: true
23
+
24
+ - type: textarea
25
+ id: repro
26
+ attributes:
27
+ label: Command or code
28
+ description: The smallest command or snippet that shows the problem.
29
+ render: shell
30
+ placeholder: tdelegram chat history --chat somechat --limit 5
31
+ validations:
32
+ required: true
33
+
34
+ - type: textarea
35
+ id: output
36
+ attributes:
37
+ label: Output
38
+ description: >
39
+ Data goes to stdout, diagnostics to stderr, so capture both.
40
+ Re-run with `--verbose` if it is not obvious. Redact before pasting.
41
+ render: shell
42
+ validations:
43
+ required: false
44
+
45
+ - type: input
46
+ id: tdelegram-version
47
+ attributes:
48
+ label: TDelegram version
49
+ placeholder: "0.1.0, or a commit sha"
50
+ validations:
51
+ required: true
52
+
53
+ - type: input
54
+ id: tdlib-version
55
+ attributes:
56
+ label: TDLib version
57
+ description: >
58
+ Many issues are really TDLib version differences. Your authorized
59
+ session reports it, or check your install.
60
+ placeholder: "1.8.67"
61
+ validations:
62
+ required: true
63
+
64
+ - type: dropdown
65
+ id: install
66
+ attributes:
67
+ label: How is libtdjson installed?
68
+ options:
69
+ - Homebrew (macOS)
70
+ - Built from source
71
+ - Docker image from this repo
72
+ - Distribution package
73
+ - Other / not sure
74
+ validations:
75
+ required: true
76
+
77
+ - type: input
78
+ id: platform
79
+ attributes:
80
+ label: OS and Python version
81
+ placeholder: "macOS 15.6 / Python 3.12.4"
82
+ validations:
83
+ required: true
84
+
85
+ - type: checkboxes
86
+ id: gate
87
+ attributes:
88
+ label: Does this involve the write gate?
89
+ description: >
90
+ A method classified read/write/destructive in `methods.json`. A wrong
91
+ verdict is a safety bug and gets priority.
92
+ options:
93
+ - label: A mutating call went through without `--yes`
94
+ - label: A read-only call was refused as a write
95
+ - label: The verdict for a method looks wrong
96
+ - label: Not related to the gate
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/bulanovdm/TDelegram/security/advisories/new
5
+ about: Report privately. Never open a public issue for a security problem.
@@ -0,0 +1,38 @@
1
+ name: Feature request
2
+ description: Suggest a capability or an ergonomics change
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What are you trying to do?
9
+ description: The task, not the solution. What are you working around today?
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: proposal
15
+ attributes:
16
+ label: What would you like instead?
17
+ validations:
18
+ required: true
19
+
20
+ - type: input
21
+ id: tdlib-method
22
+ attributes:
23
+ label: Relevant TDLib method, if you know it
24
+ description: >
25
+ All 1022 are already reachable via `tdelegram call --request`, so this
26
+ is usually about ergonomics rather than coverage. Naming the method
27
+ helps either way.
28
+ placeholder: "getChatSponsoredMessages"
29
+ validations:
30
+ required: false
31
+
32
+ - type: checkboxes
33
+ id: mutating
34
+ attributes:
35
+ label: Does it mutate anything?
36
+ options:
37
+ - label: Yes, it writes or deletes (it will need the confirmation gate)
38
+ - label: No, it is read-only
@@ -0,0 +1,11 @@
1
+ ## What this changes
2
+
3
+ <!-- And why. Link an issue if there is one. -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] `pytest` passes (no account, no network, no TDLib required)
8
+ - [ ] `ruff check src tests scripts` and `mypy --strict src/` are clean
9
+ - [ ] Mutating behaviour has a test proving the gate previews without `--yes`
10
+ - [ ] `methods.json` was regenerated, not hand-edited, if the schema changed
11
+ - [ ] No session data, `api_hash`, or real message content in the diff or tests
@@ -0,0 +1,43 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+ workflow_call: # reused as the release gate
7
+
8
+ env:
9
+ # TDLib generates td_api.h at build time, so CI reads the scheme file it is
10
+ # generated from instead of building TDLib (~20 min). Both parse to the same
11
+ # function set. Bump this together with methods.json and the expected count
12
+ # in tests/contract/test_registry.py.
13
+ TDLIB_COMMIT: d1085f9cebc5a62379991ae1652673954f229c1f
14
+
15
+ jobs:
16
+ test:
17
+ runs-on: ubuntu-latest
18
+ strategy:
19
+ fail-fast: false
20
+ matrix:
21
+ # The floor and the ceiling of what pyproject claims to support.
22
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
23
+ steps:
24
+ - uses: actions/checkout@v4
25
+ - uses: actions/setup-python@v5
26
+ with:
27
+ python-version: ${{ matrix.python-version }}
28
+ - run: pip install -e ".[dev]"
29
+
30
+ - name: Fetch pinned TDLib scheme
31
+ run: |
32
+ curl -fsSL -o "$RUNNER_TEMP/td_api.tl" \
33
+ "https://raw.githubusercontent.com/tdlib/td/$TDLIB_COMMIT/td/generate/scheme/td_api.tl"
34
+ echo "TDELEGRAM_TD_API=$RUNNER_TEMP/td_api.tl" >> "$GITHUB_ENV"
35
+
36
+ - name: Registry and request shapes are in sync with the schema
37
+ run: |
38
+ python scripts/generate_method_registry.py --output src/tdelegram/methods.json
39
+ git diff --exit-code -- src/tdelegram/methods.json src/tdelegram/schema.json
40
+
41
+ - run: pytest -q
42
+ - run: ruff check src tests scripts
43
+ - run: mypy --strict src/
@@ -0,0 +1,195 @@
1
+ name: docker
2
+
3
+ # Publishes ghcr.io/bulanovdm/tdelegram so the documented install is a pull
4
+ # rather than a 20-minute TDLib compile on the user's machine. That is the only
5
+ # thing that makes Docker a reasonable default.
6
+ #
7
+ # Tags, and what moves them (RELEASING.md has the whole scheme):
8
+ #
9
+ # push to main main, sha-<commit>
10
+ # release v1.2.3 1.2.3, and 1.2, 1 and latest where it is the newest
11
+ # pre-release v1.3.0rc1 1.3.0rc1
12
+ #
13
+ # `latest` is the newest release, never main. Release images come only from
14
+ # release.yml, which calls this workflow once the suite has passed and the tag
15
+ # agrees with pyproject.toml, with the tags scripts/release.py chose: which
16
+ # floating tags a release takes depends on every other release, and that is
17
+ # decided, and tested, in one place.
18
+
19
+ on:
20
+ push:
21
+ branches: [main]
22
+ workflow_call:
23
+ inputs:
24
+ version:
25
+ description: The version being released, which the image must report
26
+ type: string
27
+ required: true
28
+ tags:
29
+ description: Every tag the release takes, separated by spaces
30
+ type: string
31
+ required: true
32
+ # Rebuilds a branch. A release's image is never rebuilt in place, not even
33
+ # for a base image fix: that is a patch release (RELEASING.md).
34
+ workflow_dispatch:
35
+
36
+ env:
37
+ REGISTRY: ghcr.io
38
+
39
+ jobs:
40
+ # Decided once, so the per-arch images and the manifest list cannot disagree
41
+ # about which build they are.
42
+ meta:
43
+ runs-on: ubuntu-latest
44
+ permissions: {}
45
+ outputs:
46
+ image: ${{ steps.meta.outputs.image }}
47
+ tags: ${{ steps.meta.outputs.tags }}
48
+ version: ${{ steps.meta.outputs.version }}
49
+ steps:
50
+ - id: meta
51
+ env:
52
+ RELEASE_VERSION: ${{ inputs.version }}
53
+ RELEASE_TAGS: ${{ inputs.tags }}
54
+ DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
55
+ run: |
56
+ # A ghcr.io repository must be lowercase, but github.repository keeps
57
+ # the repo's own capitalisation (bulanovdm/TDelegram), which buildx
58
+ # rejects as an invalid reference.
59
+ echo "image=${REGISTRY}/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
60
+ if [[ -n "$RELEASE_VERSION" ]]; then
61
+ version="$RELEASE_VERSION"
62
+ tags="$RELEASE_TAGS"
63
+ elif [[ "$GITHUB_REF" == refs/heads/* ]]; then
64
+ # A branch name becomes a tag once what a tag cannot hold is a dash.
65
+ version="$(printf '%s' "$GITHUB_REF_NAME" | tr -c 'A-Za-z0-9_.-' '-')"
66
+ version="${version:0:128}"
67
+ tags="$version"
68
+ if [[ "$GITHUB_REF_NAME" == "$DEFAULT_BRANCH" ]]; then
69
+ tags+=" sha-${GITHUB_SHA::7}"
70
+ fi
71
+ else
72
+ echo "::error::Release images are published by release.yml. Run this on a branch." >&2
73
+ exit 1
74
+ fi
75
+ echo "version=$version" >> "$GITHUB_OUTPUT"
76
+ echo "tags=$tags" >> "$GITHUB_OUTPUT"
77
+
78
+ # Built on native runners per architecture. TDLib under QEMU emulation takes
79
+ # hours, and Apple Silicon needs arm64, so neither arch can simply be dropped.
80
+ build:
81
+ needs: meta
82
+ strategy:
83
+ fail-fast: false
84
+ matrix:
85
+ include:
86
+ - platform: linux/amd64
87
+ runner: ubuntu-latest
88
+ - platform: linux/arm64
89
+ runner: ubuntu-24.04-arm
90
+ runs-on: ${{ matrix.runner }}
91
+ permissions:
92
+ contents: read
93
+ packages: write
94
+ env:
95
+ IMAGE: ${{ needs.meta.outputs.image }}
96
+ steps:
97
+ - uses: actions/checkout@v4
98
+
99
+ - uses: docker/setup-buildx-action@v3
100
+
101
+ - uses: docker/login-action@v3
102
+ with:
103
+ registry: ${{ env.REGISTRY }}
104
+ username: ${{ github.actor }}
105
+ password: ${{ secrets.GITHUB_TOKEN }}
106
+
107
+ - name: Build and push by digest
108
+ id: build
109
+ uses: docker/build-push-action@v6
110
+ with:
111
+ context: .
112
+ platforms: ${{ matrix.platform }}
113
+ # What `docker inspect` answers when asked which build an image is.
114
+ labels: |
115
+ org.opencontainers.image.version=${{ needs.meta.outputs.version }}
116
+ org.opencontainers.image.revision=${{ github.sha }}
117
+ outputs: type=image,name=${{ env.IMAGE }},push-by-digest=true,name-canonical=true,push=true
118
+ cache-from: type=gha,scope=${{ matrix.platform }}
119
+ cache-to: type=gha,mode=max,scope=${{ matrix.platform }}
120
+
121
+ # Each architecture runs by digest, on its own runner, before anything
122
+ # is tagged: a broken image never becomes `latest`, and arm64 is tried
123
+ # natively rather than not at all.
124
+ - name: Smoke-test the image
125
+ env:
126
+ DIGEST: ${{ steps.build.outputs.digest }}
127
+ RELEASE_VERSION: ${{ inputs.version }}
128
+ run: |
129
+ reported="$(docker run --rm "$IMAGE@$DIGEST" version)"
130
+ echo "tdelegram version: $reported"
131
+ if [[ -n "$RELEASE_VERSION" && "$reported" != "$RELEASE_VERSION" ]]; then
132
+ echo "::error::The image reports $reported but would be tagged $RELEASE_VERSION." >&2
133
+ exit 1
134
+ fi
135
+ # The registry must ship inside the image: the write gate fails closed
136
+ # without it, so an image missing methods.json refuses every call.
137
+ docker run --rm --entrypoint python "$IMAGE@$DIGEST" \
138
+ -c "from tdelegram import safety; assert safety.verdict('sendMessage') == 'write'; print('gate ok')"
139
+
140
+ - name: Export the digest
141
+ # The filename is the bare hex: upload-artifact rejects a colon, so the
142
+ # "sha256:" prefix cannot stay. The manifest job puts it back.
143
+ env:
144
+ DIGEST: ${{ steps.build.outputs.digest }}
145
+ run: |
146
+ mkdir -p /tmp/digests
147
+ touch "/tmp/digests/${DIGEST#sha256:}"
148
+
149
+ - uses: actions/upload-artifact@v4
150
+ with:
151
+ name: digest-${{ strategy.job-index }}
152
+ path: /tmp/digests/*
153
+ retention-days: 1
154
+
155
+ # One multi-arch manifest list per tag, each pointing at both per-arch images.
156
+ manifest:
157
+ needs: [meta, build]
158
+ runs-on: ubuntu-latest
159
+ permissions:
160
+ contents: read
161
+ packages: write
162
+ env:
163
+ IMAGE: ${{ needs.meta.outputs.image }}
164
+ IMAGE_TAGS: ${{ needs.meta.outputs.tags }}
165
+ steps:
166
+ - uses: actions/download-artifact@v4
167
+ with:
168
+ path: /tmp/digests
169
+ pattern: digest-*
170
+ merge-multiple: true
171
+
172
+ - uses: docker/setup-buildx-action@v3
173
+ - uses: docker/login-action@v3
174
+ with:
175
+ registry: ${{ env.REGISTRY }}
176
+ username: ${{ github.actor }}
177
+ password: ${{ secrets.GITHUB_TOKEN }}
178
+
179
+ - name: Tag both images
180
+ working-directory: /tmp/digests
181
+ run: |
182
+ read -ra tags <<< "$IMAGE_TAGS"
183
+ args=()
184
+ for tag in "${tags[@]}"; do args+=(-t "$IMAGE:$tag"); done
185
+ for digest in *; do args+=("$IMAGE@sha256:$digest"); done
186
+ docker buildx imagetools create "${args[@]}"
187
+ {
188
+ echo "### Published"
189
+ for tag in "${tags[@]}"; do echo "- \`$IMAGE:$tag\`"; done
190
+ } >> "$GITHUB_STEP_SUMMARY"
191
+
192
+ - name: Show the published manifest list
193
+ env:
194
+ VERSION: ${{ needs.meta.outputs.version }}
195
+ run: docker buildx imagetools inspect "$IMAGE:$VERSION"
@@ -0,0 +1,155 @@
1
+ name: release
2
+
3
+ # A version tag is a release:
4
+ # git tag v0.2.0 && git push origin v0.2.0
5
+ #
6
+ # Nothing is published until the suite passes on every supported interpreter
7
+ # and the tag, pyproject.toml and CHANGELOG.md agree on the version. Then, one
8
+ # after another, so that a failure stops everything after it: the wheel is
9
+ # built and tried, the Docker image is built, tried and tagged, PyPI gets the
10
+ # sdist and the wheel, and a GitHub release carries both files with the
11
+ # version's CHANGELOG.md section as its notes. PyPI never accepts a version
12
+ # twice, so it comes after everything that can still fail. RELEASING.md says
13
+ # what to do when a step does.
14
+ #
15
+ # PyPI authentication is Trusted Publishing (OIDC). There is no API token
16
+ # anywhere: no repository secret to leak, rotate, or scope wrongly. The
17
+ # one-time setup is on PyPI, not here -- see RELEASING.md.
18
+
19
+ on:
20
+ push:
21
+ tags: ["v[0-9]*"]
22
+
23
+ permissions:
24
+ contents: read
25
+
26
+ jobs:
27
+ # Never publish something the suite has not passed on every supported
28
+ # interpreter. A tag is not a reason to skip the gate.
29
+ verify:
30
+ uses: ./.github/workflows/ci.yml
31
+
32
+ # The tag must be "v" plus pyproject.toml's version, and CHANGELOG.md must
33
+ # have a dated section for it. Also decides, from every other release, which
34
+ # floating image tags this one takes and whether GitHub calls it "Latest".
35
+ check:
36
+ runs-on: ubuntu-latest
37
+ outputs:
38
+ version: ${{ steps.release.outputs.version }}
39
+ prerelease: ${{ steps.release.outputs.prerelease }}
40
+ latest: ${{ steps.release.outputs.latest }}
41
+ docker_tags: ${{ steps.release.outputs.docker_tags }}
42
+ steps:
43
+ - uses: actions/checkout@v4
44
+ with:
45
+ fetch-depth: 0 # every tag, to tell whether a newer release exists
46
+ - uses: actions/setup-python@v5
47
+ with:
48
+ python-version: "3.12"
49
+ - run: pip install packaging
50
+
51
+ - id: release
52
+ run: |
53
+ python scripts/release.py "$GITHUB_REF_NAME" \
54
+ --tags "$(git tag --list 'v*')" \
55
+ --image "ghcr.io/${GITHUB_REPOSITORY,,}" \
56
+ --notes "$RUNNER_TEMP/notes.md" >> "$GITHUB_OUTPUT"
57
+
58
+ - uses: actions/upload-artifact@v4
59
+ with:
60
+ name: notes
61
+ path: ${{ runner.temp }}/notes.md
62
+
63
+ build:
64
+ needs: [verify, check]
65
+ runs-on: ubuntu-latest
66
+ steps:
67
+ - uses: actions/checkout@v4
68
+ - uses: actions/setup-python@v5
69
+ with:
70
+ python-version: "3.12"
71
+
72
+ - run: pip install build twine
73
+ - run: python -m build
74
+ - run: python -m twine check dist/*
75
+
76
+ - name: Install the wheel somewhere clean and run it
77
+ env:
78
+ VERSION: ${{ needs.check.outputs.version }}
79
+ run: |
80
+ python -m venv "$RUNNER_TEMP/relcheck"
81
+ "$RUNNER_TEMP/relcheck/bin/pip" install "dist/tdelegram-$VERSION-py3-none-any.whl"
82
+ reported="$("$RUNNER_TEMP/relcheck/bin/tdelegram" version)"
83
+ if [ "$reported" != "$VERSION" ]; then
84
+ echo "::error::the wheel reports $reported, not $VERSION" >&2
85
+ exit 1
86
+ fi
87
+ # methods.json must ship in the wheel: the write gate fails closed
88
+ # without it, so a wheel missing it refuses every call.
89
+ "$RUNNER_TEMP/relcheck/bin/python" -c \
90
+ "from tdelegram import safety; assert safety.verdict('sendMessage') == 'write'; print('gate ok')"
91
+
92
+ - uses: actions/upload-artifact@v4
93
+ with:
94
+ name: dist
95
+ path: dist/
96
+
97
+ # Builds and tries the image on both architectures, then tags it. After the
98
+ # wheel, so a failure anywhere before this point leaves nothing published.
99
+ docker:
100
+ needs: [verify, check, build]
101
+ uses: ./.github/workflows/docker.yml
102
+ permissions:
103
+ contents: read
104
+ packages: write
105
+ with:
106
+ version: ${{ needs.check.outputs.version }}
107
+ tags: ${{ needs.check.outputs.docker_tags }}
108
+
109
+ pypi:
110
+ needs: [build, docker]
111
+ runs-on: ubuntu-latest
112
+ environment: pypi
113
+ permissions:
114
+ id-token: write # the OIDC token that stands in for an API token
115
+ steps:
116
+ - uses: actions/download-artifact@v4
117
+ with:
118
+ name: dist
119
+ path: dist/
120
+ - uses: pypa/gh-action-pypi-publish@release/v1
121
+
122
+ # Last, so the release page never lists something that is not out yet.
123
+ github-release:
124
+ needs: [check, pypi]
125
+ runs-on: ubuntu-latest
126
+ permissions:
127
+ contents: write
128
+ steps:
129
+ - uses: actions/download-artifact@v4
130
+ with:
131
+ name: dist
132
+ path: dist/
133
+ - uses: actions/download-artifact@v4
134
+ with:
135
+ name: notes
136
+
137
+ - name: Publish the release
138
+ env:
139
+ GH_TOKEN: ${{ github.token }}
140
+ GH_REPO: ${{ github.repository }}
141
+ VERSION: ${{ needs.check.outputs.version }}
142
+ PRERELEASE: ${{ needs.check.outputs.prerelease }}
143
+ LATEST: ${{ needs.check.outputs.latest }}
144
+ run: |
145
+ if gh release view "$GITHUB_REF_NAME" > /dev/null 2>&1; then
146
+ # A re-run after the release was made: only make sure the files are on it.
147
+ gh release upload "$GITHUB_REF_NAME" dist/* --clobber
148
+ else
149
+ gh release create "$GITHUB_REF_NAME" dist/* \
150
+ --verify-tag \
151
+ --title "TDelegram $VERSION" \
152
+ --notes-file notes.md \
153
+ --prerelease="$PRERELEASE" \
154
+ --latest="$LATEST"
155
+ fi