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.
- tdelegram-0.1.0/.env.example +8 -0
- tdelegram-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +96 -0
- tdelegram-0.1.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
- tdelegram-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +38 -0
- tdelegram-0.1.0/.github/PULL_REQUEST_TEMPLATE.md +11 -0
- tdelegram-0.1.0/.github/workflows/ci.yml +43 -0
- tdelegram-0.1.0/.github/workflows/docker.yml +195 -0
- tdelegram-0.1.0/.github/workflows/release.yml +155 -0
- tdelegram-0.1.0/.gitignore +233 -0
- tdelegram-0.1.0/CHANGELOG.md +332 -0
- tdelegram-0.1.0/CLAUDE.md +224 -0
- tdelegram-0.1.0/CODE_OF_CONDUCT.md +137 -0
- tdelegram-0.1.0/CONTRIBUTING.md +22 -0
- tdelegram-0.1.0/Dockerfile +54 -0
- tdelegram-0.1.0/LICENSE +201 -0
- tdelegram-0.1.0/NOTICE +5 -0
- tdelegram-0.1.0/PKG-INFO +200 -0
- tdelegram-0.1.0/README.md +158 -0
- tdelegram-0.1.0/RELEASING.md +169 -0
- tdelegram-0.1.0/SECURITY.md +12 -0
- tdelegram-0.1.0/pyproject.toml +87 -0
- tdelegram-0.1.0/scripts/generate_method_registry.py +389 -0
- tdelegram-0.1.0/scripts/release.py +266 -0
- tdelegram-0.1.0/skills/tdelegram/SKILL.md +290 -0
- tdelegram-0.1.0/skills/tdelegram/evals/evals.json +51 -0
- tdelegram-0.1.0/skills/tdelegram/references/commands.md +216 -0
- tdelegram-0.1.0/skills/tdelegram/references/library.md +229 -0
- tdelegram-0.1.0/skills/tdelegram/references/recipes.md +242 -0
- tdelegram-0.1.0/skills/tdelegram/references/setup.md +226 -0
- tdelegram-0.1.0/skills/tdelegram/references/troubleshooting.md +166 -0
- tdelegram-0.1.0/src/tdelegram/__init__.py +42 -0
- tdelegram-0.1.0/src/tdelegram/api/__init__.py +47 -0
- tdelegram-0.1.0/src/tdelegram/api/account.py +69 -0
- tdelegram-0.1.0/src/tdelegram/api/admin.py +225 -0
- tdelegram-0.1.0/src/tdelegram/api/bots.py +134 -0
- tdelegram-0.1.0/src/tdelegram/api/chats.py +314 -0
- tdelegram-0.1.0/src/tdelegram/api/contacts.py +78 -0
- tdelegram-0.1.0/src/tdelegram/api/drafts.py +49 -0
- tdelegram-0.1.0/src/tdelegram/api/export.py +275 -0
- tdelegram-0.1.0/src/tdelegram/api/folders.py +77 -0
- tdelegram-0.1.0/src/tdelegram/api/inbox.py +124 -0
- tdelegram-0.1.0/src/tdelegram/api/media.py +202 -0
- tdelegram-0.1.0/src/tdelegram/api/messages.py +400 -0
- tdelegram-0.1.0/src/tdelegram/api/polls.py +51 -0
- tdelegram-0.1.0/src/tdelegram/api/proxies.py +222 -0
- tdelegram-0.1.0/src/tdelegram/api/reactions.py +43 -0
- tdelegram-0.1.0/src/tdelegram/api/search.py +103 -0
- tdelegram-0.1.0/src/tdelegram/api/secret.py +46 -0
- tdelegram-0.1.0/src/tdelegram/api/stories.py +42 -0
- tdelegram-0.1.0/src/tdelegram/api/topics.py +161 -0
- tdelegram-0.1.0/src/tdelegram/api/updates.py +106 -0
- tdelegram-0.1.0/src/tdelegram/api/users.py +82 -0
- tdelegram-0.1.0/src/tdelegram/auth.py +433 -0
- tdelegram-0.1.0/src/tdelegram/cli/__init__.py +0 -0
- tdelegram-0.1.0/src/tdelegram/cli/context.py +301 -0
- tdelegram-0.1.0/src/tdelegram/cli/main.py +1050 -0
- tdelegram-0.1.0/src/tdelegram/cli/output.py +107 -0
- tdelegram-0.1.0/src/tdelegram/client.py +198 -0
- tdelegram-0.1.0/src/tdelegram/config.py +113 -0
- tdelegram-0.1.0/src/tdelegram/credentials.py +227 -0
- tdelegram-0.1.0/src/tdelegram/dates.py +65 -0
- tdelegram-0.1.0/src/tdelegram/entities.py +91 -0
- tdelegram-0.1.0/src/tdelegram/errors.py +127 -0
- tdelegram-0.1.0/src/tdelegram/files.py +100 -0
- tdelegram-0.1.0/src/tdelegram/loop.py +197 -0
- tdelegram-0.1.0/src/tdelegram/methods.json +4090 -0
- tdelegram-0.1.0/src/tdelegram/normalize.py +378 -0
- tdelegram-0.1.0/src/tdelegram/paging.py +127 -0
- tdelegram-0.1.0/src/tdelegram/py.typed +0 -0
- tdelegram-0.1.0/src/tdelegram/safety.py +43 -0
- tdelegram-0.1.0/src/tdelegram/schema.json +3211 -0
- tdelegram-0.1.0/src/tdelegram/schema.py +233 -0
- tdelegram-0.1.0/src/tdelegram/tdjson.py +54 -0
- tdelegram-0.1.0/src/tdelegram/transport.py +196 -0
- tdelegram-0.1.0/tests/__init__.py +0 -0
- tdelegram-0.1.0/tests/conftest.py +64 -0
- tdelegram-0.1.0/tests/contract/__init__.py +0 -0
- tdelegram-0.1.0/tests/contract/test_registry.py +212 -0
- tdelegram-0.1.0/tests/integration/__init__.py +0 -0
- tdelegram-0.1.0/tests/integration/test_live.py +160 -0
- tdelegram-0.1.0/tests/unit/__init__.py +0 -0
- tdelegram-0.1.0/tests/unit/test_auth.py +286 -0
- tdelegram-0.1.0/tests/unit/test_cli.py +1816 -0
- tdelegram-0.1.0/tests/unit/test_coverage.py +1038 -0
- tdelegram-0.1.0/tests/unit/test_credentials.py +350 -0
- tdelegram-0.1.0/tests/unit/test_export.py +259 -0
- tdelegram-0.1.0/tests/unit/test_loop.py +136 -0
- tdelegram-0.1.0/tests/unit/test_normalize.py +372 -0
- tdelegram-0.1.0/tests/unit/test_proxies.py +91 -0
- tdelegram-0.1.0/tests/unit/test_release.py +274 -0
- tdelegram-0.1.0/tests/unit/test_safety.py +84 -0
- tdelegram-0.1.0/tests/unit/test_schema.py +152 -0
|
@@ -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,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
|