tgpost 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.
- tgpost-0.1.0/.gitignore +60 -0
- tgpost-0.1.0/CHANGELOG.md +36 -0
- tgpost-0.1.0/DISCLAIMER.md +55 -0
- tgpost-0.1.0/LICENSE +21 -0
- tgpost-0.1.0/PKG-INFO +247 -0
- tgpost-0.1.0/README.md +211 -0
- tgpost-0.1.0/pyproject.toml +98 -0
- tgpost-0.1.0/tgpost/__init__.py +38 -0
- tgpost-0.1.0/tgpost/__main__.py +12 -0
- tgpost-0.1.0/tgpost/cli.py +482 -0
- tgpost-0.1.0/tgpost/client.py +557 -0
- tgpost-0.1.0/tgpost/config.py +249 -0
- tgpost-0.1.0/tgpost/errors.py +82 -0
- tgpost-0.1.0/tgpost/formatting.py +160 -0
- tgpost-0.1.0/tgpost/limits.py +168 -0
- tgpost-0.1.0/tgpost/py.typed +0 -0
- tgpost-0.1.0/tgpost/scheduler.py +489 -0
- tgpost-0.1.0/tgpost/store.py +198 -0
tgpost-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Environment
|
|
2
|
+
env/
|
|
3
|
+
venv/
|
|
4
|
+
ENV/
|
|
5
|
+
VENV/
|
|
6
|
+
.venv/
|
|
7
|
+
|
|
8
|
+
# Python cache
|
|
9
|
+
__pycache__/
|
|
10
|
+
*.py[cod]
|
|
11
|
+
*$py.class
|
|
12
|
+
*.so
|
|
13
|
+
.Python
|
|
14
|
+
|
|
15
|
+
# Build and distribution
|
|
16
|
+
build/
|
|
17
|
+
dist/
|
|
18
|
+
*.egg-info/
|
|
19
|
+
.eggs/
|
|
20
|
+
*.egg
|
|
21
|
+
*.whl
|
|
22
|
+
*.tar.gz
|
|
23
|
+
pip-log.txt
|
|
24
|
+
pip-delete-this-directory.txt
|
|
25
|
+
|
|
26
|
+
# Testing / typing / linting
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
htmlcov/
|
|
30
|
+
.mypy_cache/
|
|
31
|
+
.ruff_cache/
|
|
32
|
+
.tox/
|
|
33
|
+
|
|
34
|
+
# Data and downloads
|
|
35
|
+
dl/
|
|
36
|
+
downloads/
|
|
37
|
+
data/
|
|
38
|
+
|
|
39
|
+
# Databases
|
|
40
|
+
*.db
|
|
41
|
+
*.sqlite
|
|
42
|
+
*.sqlite3
|
|
43
|
+
|
|
44
|
+
# Secrets / local config
|
|
45
|
+
.env
|
|
46
|
+
*.local
|
|
47
|
+
|
|
48
|
+
# Temporary files
|
|
49
|
+
# Local working notes (per-chat session summaries; never committed)
|
|
50
|
+
SCRATCHPAD.md
|
|
51
|
+
# Plan-mode plans and other local scratch notes
|
|
52
|
+
scratchpad/
|
|
53
|
+
|
|
54
|
+
# IDE and OS
|
|
55
|
+
.DS_Store
|
|
56
|
+
.vscode/
|
|
57
|
+
.idea/
|
|
58
|
+
*.swp
|
|
59
|
+
*.swo
|
|
60
|
+
*~
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
|
|
5
|
+
[semantic versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-09-05
|
|
8
|
+
|
|
9
|
+
First release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `TelegramClient`, a synchronous Bot API client covering `sendMessage`,
|
|
14
|
+
`sendPhoto`, `sendDocument`, `sendVideo`, `sendAudio`, `sendAnimation`,
|
|
15
|
+
`sendVoice` and `sendMediaGroup`, plus `getMe`, `getChat` and `getChatMember`.
|
|
16
|
+
- Automatic retry that honours the `retry_after` Telegram returns on a 429,
|
|
17
|
+
retries 5xx and transport failures with capped exponential backoff, and never
|
|
18
|
+
retries a 400, 401 or 403.
|
|
19
|
+
- Outbound rate limiting: about one message per second per chat and thirty per
|
|
20
|
+
second overall, matching the documented Bot API limits.
|
|
21
|
+
- Text over 4096 characters is split across messages at paragraph, line or word
|
|
22
|
+
boundaries.
|
|
23
|
+
- HTML and MarkdownV2 escaping, with HTML as the default parse mode.
|
|
24
|
+
- Upload size checks that run before the request and name the local Bot API
|
|
25
|
+
server as the way past the 50 MB limit.
|
|
26
|
+
- Persistent scheduling behind the `[schedule]` extra: one-shot, interval and
|
|
27
|
+
cron jobs stored in SQLite, surviving a restart, with a one-hour misfire grace
|
|
28
|
+
window and coalescing of missed repeats.
|
|
29
|
+
- CLI: `send`, `schedule`, `jobs`, `cancel`, `run-due`, `daemon`, `history`,
|
|
30
|
+
`targets` and `check`.
|
|
31
|
+
- Named channel targets in a TOML config file, so a send names a channel rather
|
|
32
|
+
than repeating a token and a chat id.
|
|
33
|
+
- `tgpost check`, which reports whether the bot can actually post to each
|
|
34
|
+
configured channel before a real send fails.
|
|
35
|
+
|
|
36
|
+
[0.1.0]: https://github.com/pandiyarajk/tgpost/releases/tag/v0.1.0
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Disclaimer
|
|
2
|
+
|
|
3
|
+
**tgpost is provided AS IS, without warranty of any kind, express or implied.
|
|
4
|
+
Use it entirely at your own risk.**
|
|
5
|
+
|
|
6
|
+
No warranty is given as to merchantability, fitness for a particular purpose, or
|
|
7
|
+
non-infringement. To the maximum extent permitted by law, the author accepts no
|
|
8
|
+
liability for any claim, damages or other liability arising from the use of this
|
|
9
|
+
software, including but not limited to:
|
|
10
|
+
|
|
11
|
+
- messages or files published to the wrong channel, or to the wrong audience
|
|
12
|
+
- content published at the wrong time, or repeatedly
|
|
13
|
+
- data loss or corruption
|
|
14
|
+
- business interruption, reputational harm, or consequential damages
|
|
15
|
+
- suspension or termination of your Telegram bot or account
|
|
16
|
+
|
|
17
|
+
## Your responsibilities
|
|
18
|
+
|
|
19
|
+
You are responsible for:
|
|
20
|
+
|
|
21
|
+
- **verifying the target channel** before sending. A chat id is a number, and a
|
|
22
|
+
wrong one sends to a real audience that was never meant to receive it. Use
|
|
23
|
+
`tgpost check` and `--dry-run` first.
|
|
24
|
+
- **what you publish.** This tool transmits whatever you give it.
|
|
25
|
+
- **being authorised** to post in the channel and to distribute the content.
|
|
26
|
+
- **complying with the Telegram Terms of Service** and the Bot API terms,
|
|
27
|
+
including their rate limits and anti-spam rules.
|
|
28
|
+
- **protecting your bot token.** A leaked token lets anyone post as your bot.
|
|
29
|
+
Keep it in an environment variable, not in a committed file.
|
|
30
|
+
|
|
31
|
+
## Unattended operation
|
|
32
|
+
|
|
33
|
+
`tgpost schedule` and `tgpost daemon` publish **without a human present**. A
|
|
34
|
+
mistaken cron expression can post to a live channel repeatedly, at any hour,
|
|
35
|
+
until someone stops it. Test a new schedule against a private channel before
|
|
36
|
+
pointing it at a real audience.
|
|
37
|
+
|
|
38
|
+
## Not certified for high-assurance use
|
|
39
|
+
|
|
40
|
+
This software is not certified, validated or approved for use in regulated,
|
|
41
|
+
forensic, safety-critical or high-assurance environments, and must not be relied
|
|
42
|
+
upon where failure to deliver, or delivery of the wrong content, could cause
|
|
43
|
+
injury, legal exposure or significant loss. It offers no delivery guarantee: the
|
|
44
|
+
Telegram Bot API provides no idempotency key, so a send that times out may or may
|
|
45
|
+
not have been delivered.
|
|
46
|
+
|
|
47
|
+
## The licence governs
|
|
48
|
+
|
|
49
|
+
This document is a plain-language summary offered for convenience. **The
|
|
50
|
+
[MIT LICENSE](LICENSE) is the governing legal text and prevails** wherever the
|
|
51
|
+
two differ. Nothing here grants rights beyond, or narrows, that licence.
|
|
52
|
+
|
|
53
|
+
The author is not a lawyer and this is not legal advice. A disclaimer reduces
|
|
54
|
+
legal risk; it does not remove it. Seek professional advice for anything
|
|
55
|
+
high-stakes.
|
tgpost-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pandiyaraj Karuppasamy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tgpost-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tgpost
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Send messages and files to Telegram channels, now or on a schedule
|
|
5
|
+
Project-URL: Repository, https://github.com/pandiyarajk/tgpost
|
|
6
|
+
Project-URL: Issues, https://github.com/pandiyarajk/tgpost/issues
|
|
7
|
+
Author-email: Pandiyaraj Karuppasamy <pandiyarajk@live.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: bot,cli,notifications,scheduler,telegram
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Communications :: Chat
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: tzdata; platform_system == 'Windows'
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: apscheduler<4,>=3.11; extra == 'dev'
|
|
25
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: mypy>=1.8; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Requires-Dist: sqlalchemy>=1.4; extra == 'dev'
|
|
31
|
+
Requires-Dist: twine>=4.0; extra == 'dev'
|
|
32
|
+
Provides-Extra: schedule
|
|
33
|
+
Requires-Dist: apscheduler<4,>=3.11; extra == 'schedule'
|
|
34
|
+
Requires-Dist: sqlalchemy>=1.4; extra == 'schedule'
|
|
35
|
+
Description-Content-Type: text/markdown
|
|
36
|
+
|
|
37
|
+
# tgpost
|
|
38
|
+
|
|
39
|
+
Send messages and files to Telegram channels, now or on a schedule.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
> ### Disclaimer
|
|
44
|
+
>
|
|
45
|
+
> **Provided AS IS, without warranty of any kind, express or implied. Use
|
|
46
|
+
> entirely at your own risk.** The author accepts no liability for data loss,
|
|
47
|
+
> content published to the wrong channel or audience, business interruption or
|
|
48
|
+
> consequential damages.
|
|
49
|
+
>
|
|
50
|
+
> **You** are responsible for verifying the target channel, for what you publish,
|
|
51
|
+
> for being authorised to post there, and for complying with the Telegram Terms
|
|
52
|
+
> of Service. `tgpost schedule` and `tgpost daemon` publish **unattended**: a
|
|
53
|
+
> mistaken cron expression can post to a live channel repeatedly until someone
|
|
54
|
+
> stops it. Test against a private channel first.
|
|
55
|
+
>
|
|
56
|
+
> Not certified for regulated, forensic, safety-critical or high-assurance use.
|
|
57
|
+
> There is no delivery guarantee: the Bot API offers no idempotency key, so a
|
|
58
|
+
> send that times out may or may not have arrived.
|
|
59
|
+
>
|
|
60
|
+
> The [MIT LICENSE](https://github.com/pandiyarajk/tgpost/blob/main/LICENSE) is
|
|
61
|
+
> the governing text and prevails wherever this summary differs from it. See
|
|
62
|
+
> [DISCLAIMER.md](https://github.com/pandiyarajk/tgpost/blob/main/DISCLAIMER.md)
|
|
63
|
+
> for the full text.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Why this exists
|
|
68
|
+
|
|
69
|
+
Sending a file to a Telegram channel from the command line is well covered
|
|
70
|
+
already. What is missing is doing it **later**, reliably.
|
|
71
|
+
|
|
72
|
+
The Telegram HTTP Bot API has no server-side scheduling: there is no
|
|
73
|
+
`schedule_date` parameter on any send method, so "post this at 9am on weekdays"
|
|
74
|
+
has to be solved on your side, and survive a reboot. tgpost is a sender with a
|
|
75
|
+
persistent job store attached, so a scheduled post still goes out after the
|
|
76
|
+
machine restarts.
|
|
77
|
+
|
|
78
|
+
If you only need to send something right now, `telegram-send` and `apprise` both
|
|
79
|
+
do that well.
|
|
80
|
+
|
|
81
|
+
## Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install tgpost # sending only
|
|
85
|
+
pip install tgpost[schedule] # sending and scheduling
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Requires Python 3.11 or newer.
|
|
89
|
+
|
|
90
|
+
## Getting started
|
|
91
|
+
|
|
92
|
+
Create a bot with [@BotFather](https://t.me/BotFather), then add it to your
|
|
93
|
+
channel **as an administrator with permission to post messages**. A bot cannot
|
|
94
|
+
post to a channel as an ordinary member.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
export TGPOST_BOT_TOKEN="123456789:your-token-here" # Windows: set TGPOST_BOT_TOKEN=...
|
|
98
|
+
|
|
99
|
+
tgpost targets add release-notes -1001234567890 --description "Build announcements"
|
|
100
|
+
tgpost check
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`tgpost check` verifies the token and reports, per target, whether the bot can
|
|
104
|
+
actually post. Those two permission failures are the most common cause of a
|
|
105
|
+
send failing later, so it is worth running first.
|
|
106
|
+
|
|
107
|
+
### Finding a channel id
|
|
108
|
+
|
|
109
|
+
A public channel can be addressed as `@channelusername`. For a private channel,
|
|
110
|
+
post any message in it, then read the numeric id (it starts with `-100`):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
tgpost targets add private-chan @temporary_username # if public, then:
|
|
114
|
+
tgpost check # prints the numeric id
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Sending
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
tgpost send --to release-notes --text "Build 1.2.3 shipped"
|
|
121
|
+
tgpost send --to release-notes --file build.zip --caption "Nightly build"
|
|
122
|
+
tgpost send --to release-notes --file a.png --file b.png --album --kind photo
|
|
123
|
+
git log --oneline -10 | tgpost send --to release-notes --stdin
|
|
124
|
+
tgpost send --to release-notes --text "check this first" --dry-run
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Text longer than Telegram's 4096-character limit is split across several
|
|
128
|
+
messages at paragraph, line or word boundaries.
|
|
129
|
+
|
|
130
|
+
## Scheduling
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
tgpost schedule --to release-notes --text "Standup" --cron "0 9 * * 1-5" --tz Europe/London
|
|
134
|
+
tgpost schedule --to alerts --text "Still alive" --every 30m
|
|
135
|
+
tgpost schedule --to release-notes --file report.pdf --at "2026-09-10T14:00"
|
|
136
|
+
tgpost schedule --to alerts --text "in two hours" --at 2h
|
|
137
|
+
|
|
138
|
+
tgpost jobs # what is scheduled, and when each next runs
|
|
139
|
+
tgpost history # what actually happened
|
|
140
|
+
tgpost cancel a41a4c18
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Jobs are stored in SQLite and survive a restart. Something has to run them:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
tgpost daemon # long-running: keeps firing jobs until stopped
|
|
147
|
+
tgpost run-due # fires everything due, then exits
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Use `daemon` on a machine that stays up. Use `run-due` from **Windows Task
|
|
151
|
+
Scheduler** or cron if you would rather not keep a process alive:
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
schtasks /create /tn "tgpost" /tr "tgpost run-due" /sc minute /mo 5
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
A job whose time passed while nothing was running still fires when the scheduler
|
|
158
|
+
comes back, as long as it is within the one-hour grace window. Missed repeats are
|
|
159
|
+
coalesced, so a weekend of downtime sends once rather than fifty times.
|
|
160
|
+
|
|
161
|
+
## Formatting
|
|
162
|
+
|
|
163
|
+
The default parse mode is **HTML**, because it only requires `&`, `<` and `>` to
|
|
164
|
+
be escaped. MarkdownV2 requires eighteen characters to be escaped, including
|
|
165
|
+
`.`, `-` and `!`, which appear in ordinary prose, and a single missed escape is
|
|
166
|
+
a hard error rather than a cosmetic one.
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
tgpost send --to alerts --text "<b>Deploy finished</b>" # markup as written
|
|
170
|
+
tgpost send --to alerts --text "$RAW" --escape # escape untrusted text
|
|
171
|
+
tgpost send --to alerts --text "plain" --parse-mode none
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Pass `--escape` for text you did not write yourself. Without it, the text is
|
|
175
|
+
sent as markup and a stray `<` will be rejected by Telegram.
|
|
176
|
+
|
|
177
|
+
## Configuration
|
|
178
|
+
|
|
179
|
+
Config lives at `%APPDATA%\tgpost\config.toml` on Windows, or
|
|
180
|
+
`~/.config/tgpost/config.toml` elsewhere.
|
|
181
|
+
|
|
182
|
+
```toml
|
|
183
|
+
[defaults]
|
|
184
|
+
parse_mode = "html"
|
|
185
|
+
base_url = "https://api.telegram.org"
|
|
186
|
+
|
|
187
|
+
[targets.release-notes]
|
|
188
|
+
chat_id = "-1001234567890"
|
|
189
|
+
description = "Build announcements"
|
|
190
|
+
|
|
191
|
+
[targets.alerts]
|
|
192
|
+
chat_id = "@my_public_channel"
|
|
193
|
+
parse_mode = "none"
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
| Variable | Purpose |
|
|
197
|
+
|---|---|
|
|
198
|
+
| `TGPOST_BOT_TOKEN` | Bot token. **Preferred over storing it in the config file.** |
|
|
199
|
+
| `TGPOST_CONFIG` | Path to the config file. |
|
|
200
|
+
| `TGPOST_DB` | Path to the job database. |
|
|
201
|
+
|
|
202
|
+
The token is read from `--token`, then `TGPOST_BOT_TOKEN`, then the config file.
|
|
203
|
+
`tgpost targets add` never writes the token to disk, so a token supplied on the
|
|
204
|
+
command line or in the environment is not persisted by accident.
|
|
205
|
+
|
|
206
|
+
## Limits
|
|
207
|
+
|
|
208
|
+
Set by the Bot API, not by this package:
|
|
209
|
+
|
|
210
|
+
| Limit | Value |
|
|
211
|
+
|---|---|
|
|
212
|
+
| Message text | 4096 characters (tgpost splits automatically) |
|
|
213
|
+
| Caption | 1024 characters |
|
|
214
|
+
| Photo upload | 10 MB |
|
|
215
|
+
| Any other file | 50 MB |
|
|
216
|
+
| Album | 2 to 10 items |
|
|
217
|
+
| Rate | About 1 message per second per chat, 30 per second overall |
|
|
218
|
+
|
|
219
|
+
tgpost paces sends to stay under the rate limits, and on a `429` waits exactly
|
|
220
|
+
the `retry_after` Telegram asks for rather than guessing.
|
|
221
|
+
|
|
222
|
+
To send files larger than 50 MB, run a
|
|
223
|
+
[local Bot API server](https://github.com/tdlib/telegram-bot-api), which raises
|
|
224
|
+
the limit to 2000 MB, and point tgpost at it:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
tgpost --base-url http://127.0.0.1:8081 send --to release-notes --file big.iso
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Python API
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from tgpost import TelegramClient
|
|
234
|
+
|
|
235
|
+
with TelegramClient("123456789:your-token") as client:
|
|
236
|
+
client.send_message("-1001234567890", "Build finished")
|
|
237
|
+
client.send_file("-1001234567890", "report.pdf", caption="Nightly report")
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Errors derive from `TgPostError`: `AuthError`, `ForbiddenError`,
|
|
241
|
+
`BadRequestError`, `RateLimitError`, `ServerError`, `NetworkError`,
|
|
242
|
+
`FileTooLargeError`, `ConfigError` and `SchedulerError`. Retryable failures are
|
|
243
|
+
retried internally; what reaches you is a failure worth acting on.
|
|
244
|
+
|
|
245
|
+
## License
|
|
246
|
+
|
|
247
|
+
MIT. See [LICENSE](https://github.com/pandiyarajk/tgpost/blob/main/LICENSE).
|
tgpost-0.1.0/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# tgpost
|
|
2
|
+
|
|
3
|
+
Send messages and files to Telegram channels, now or on a schedule.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
> ### Disclaimer
|
|
8
|
+
>
|
|
9
|
+
> **Provided AS IS, without warranty of any kind, express or implied. Use
|
|
10
|
+
> entirely at your own risk.** The author accepts no liability for data loss,
|
|
11
|
+
> content published to the wrong channel or audience, business interruption or
|
|
12
|
+
> consequential damages.
|
|
13
|
+
>
|
|
14
|
+
> **You** are responsible for verifying the target channel, for what you publish,
|
|
15
|
+
> for being authorised to post there, and for complying with the Telegram Terms
|
|
16
|
+
> of Service. `tgpost schedule` and `tgpost daemon` publish **unattended**: a
|
|
17
|
+
> mistaken cron expression can post to a live channel repeatedly until someone
|
|
18
|
+
> stops it. Test against a private channel first.
|
|
19
|
+
>
|
|
20
|
+
> Not certified for regulated, forensic, safety-critical or high-assurance use.
|
|
21
|
+
> There is no delivery guarantee: the Bot API offers no idempotency key, so a
|
|
22
|
+
> send that times out may or may not have arrived.
|
|
23
|
+
>
|
|
24
|
+
> The [MIT LICENSE](https://github.com/pandiyarajk/tgpost/blob/main/LICENSE) is
|
|
25
|
+
> the governing text and prevails wherever this summary differs from it. See
|
|
26
|
+
> [DISCLAIMER.md](https://github.com/pandiyarajk/tgpost/blob/main/DISCLAIMER.md)
|
|
27
|
+
> for the full text.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Why this exists
|
|
32
|
+
|
|
33
|
+
Sending a file to a Telegram channel from the command line is well covered
|
|
34
|
+
already. What is missing is doing it **later**, reliably.
|
|
35
|
+
|
|
36
|
+
The Telegram HTTP Bot API has no server-side scheduling: there is no
|
|
37
|
+
`schedule_date` parameter on any send method, so "post this at 9am on weekdays"
|
|
38
|
+
has to be solved on your side, and survive a reboot. tgpost is a sender with a
|
|
39
|
+
persistent job store attached, so a scheduled post still goes out after the
|
|
40
|
+
machine restarts.
|
|
41
|
+
|
|
42
|
+
If you only need to send something right now, `telegram-send` and `apprise` both
|
|
43
|
+
do that well.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install tgpost # sending only
|
|
49
|
+
pip install tgpost[schedule] # sending and scheduling
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Requires Python 3.11 or newer.
|
|
53
|
+
|
|
54
|
+
## Getting started
|
|
55
|
+
|
|
56
|
+
Create a bot with [@BotFather](https://t.me/BotFather), then add it to your
|
|
57
|
+
channel **as an administrator with permission to post messages**. A bot cannot
|
|
58
|
+
post to a channel as an ordinary member.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export TGPOST_BOT_TOKEN="123456789:your-token-here" # Windows: set TGPOST_BOT_TOKEN=...
|
|
62
|
+
|
|
63
|
+
tgpost targets add release-notes -1001234567890 --description "Build announcements"
|
|
64
|
+
tgpost check
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`tgpost check` verifies the token and reports, per target, whether the bot can
|
|
68
|
+
actually post. Those two permission failures are the most common cause of a
|
|
69
|
+
send failing later, so it is worth running first.
|
|
70
|
+
|
|
71
|
+
### Finding a channel id
|
|
72
|
+
|
|
73
|
+
A public channel can be addressed as `@channelusername`. For a private channel,
|
|
74
|
+
post any message in it, then read the numeric id (it starts with `-100`):
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
tgpost targets add private-chan @temporary_username # if public, then:
|
|
78
|
+
tgpost check # prints the numeric id
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Sending
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
tgpost send --to release-notes --text "Build 1.2.3 shipped"
|
|
85
|
+
tgpost send --to release-notes --file build.zip --caption "Nightly build"
|
|
86
|
+
tgpost send --to release-notes --file a.png --file b.png --album --kind photo
|
|
87
|
+
git log --oneline -10 | tgpost send --to release-notes --stdin
|
|
88
|
+
tgpost send --to release-notes --text "check this first" --dry-run
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Text longer than Telegram's 4096-character limit is split across several
|
|
92
|
+
messages at paragraph, line or word boundaries.
|
|
93
|
+
|
|
94
|
+
## Scheduling
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
tgpost schedule --to release-notes --text "Standup" --cron "0 9 * * 1-5" --tz Europe/London
|
|
98
|
+
tgpost schedule --to alerts --text "Still alive" --every 30m
|
|
99
|
+
tgpost schedule --to release-notes --file report.pdf --at "2026-09-10T14:00"
|
|
100
|
+
tgpost schedule --to alerts --text "in two hours" --at 2h
|
|
101
|
+
|
|
102
|
+
tgpost jobs # what is scheduled, and when each next runs
|
|
103
|
+
tgpost history # what actually happened
|
|
104
|
+
tgpost cancel a41a4c18
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Jobs are stored in SQLite and survive a restart. Something has to run them:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
tgpost daemon # long-running: keeps firing jobs until stopped
|
|
111
|
+
tgpost run-due # fires everything due, then exits
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Use `daemon` on a machine that stays up. Use `run-due` from **Windows Task
|
|
115
|
+
Scheduler** or cron if you would rather not keep a process alive:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
schtasks /create /tn "tgpost" /tr "tgpost run-due" /sc minute /mo 5
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
A job whose time passed while nothing was running still fires when the scheduler
|
|
122
|
+
comes back, as long as it is within the one-hour grace window. Missed repeats are
|
|
123
|
+
coalesced, so a weekend of downtime sends once rather than fifty times.
|
|
124
|
+
|
|
125
|
+
## Formatting
|
|
126
|
+
|
|
127
|
+
The default parse mode is **HTML**, because it only requires `&`, `<` and `>` to
|
|
128
|
+
be escaped. MarkdownV2 requires eighteen characters to be escaped, including
|
|
129
|
+
`.`, `-` and `!`, which appear in ordinary prose, and a single missed escape is
|
|
130
|
+
a hard error rather than a cosmetic one.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
tgpost send --to alerts --text "<b>Deploy finished</b>" # markup as written
|
|
134
|
+
tgpost send --to alerts --text "$RAW" --escape # escape untrusted text
|
|
135
|
+
tgpost send --to alerts --text "plain" --parse-mode none
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Pass `--escape` for text you did not write yourself. Without it, the text is
|
|
139
|
+
sent as markup and a stray `<` will be rejected by Telegram.
|
|
140
|
+
|
|
141
|
+
## Configuration
|
|
142
|
+
|
|
143
|
+
Config lives at `%APPDATA%\tgpost\config.toml` on Windows, or
|
|
144
|
+
`~/.config/tgpost/config.toml` elsewhere.
|
|
145
|
+
|
|
146
|
+
```toml
|
|
147
|
+
[defaults]
|
|
148
|
+
parse_mode = "html"
|
|
149
|
+
base_url = "https://api.telegram.org"
|
|
150
|
+
|
|
151
|
+
[targets.release-notes]
|
|
152
|
+
chat_id = "-1001234567890"
|
|
153
|
+
description = "Build announcements"
|
|
154
|
+
|
|
155
|
+
[targets.alerts]
|
|
156
|
+
chat_id = "@my_public_channel"
|
|
157
|
+
parse_mode = "none"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| Variable | Purpose |
|
|
161
|
+
|---|---|
|
|
162
|
+
| `TGPOST_BOT_TOKEN` | Bot token. **Preferred over storing it in the config file.** |
|
|
163
|
+
| `TGPOST_CONFIG` | Path to the config file. |
|
|
164
|
+
| `TGPOST_DB` | Path to the job database. |
|
|
165
|
+
|
|
166
|
+
The token is read from `--token`, then `TGPOST_BOT_TOKEN`, then the config file.
|
|
167
|
+
`tgpost targets add` never writes the token to disk, so a token supplied on the
|
|
168
|
+
command line or in the environment is not persisted by accident.
|
|
169
|
+
|
|
170
|
+
## Limits
|
|
171
|
+
|
|
172
|
+
Set by the Bot API, not by this package:
|
|
173
|
+
|
|
174
|
+
| Limit | Value |
|
|
175
|
+
|---|---|
|
|
176
|
+
| Message text | 4096 characters (tgpost splits automatically) |
|
|
177
|
+
| Caption | 1024 characters |
|
|
178
|
+
| Photo upload | 10 MB |
|
|
179
|
+
| Any other file | 50 MB |
|
|
180
|
+
| Album | 2 to 10 items |
|
|
181
|
+
| Rate | About 1 message per second per chat, 30 per second overall |
|
|
182
|
+
|
|
183
|
+
tgpost paces sends to stay under the rate limits, and on a `429` waits exactly
|
|
184
|
+
the `retry_after` Telegram asks for rather than guessing.
|
|
185
|
+
|
|
186
|
+
To send files larger than 50 MB, run a
|
|
187
|
+
[local Bot API server](https://github.com/tdlib/telegram-bot-api), which raises
|
|
188
|
+
the limit to 2000 MB, and point tgpost at it:
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
tgpost --base-url http://127.0.0.1:8081 send --to release-notes --file big.iso
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Python API
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from tgpost import TelegramClient
|
|
198
|
+
|
|
199
|
+
with TelegramClient("123456789:your-token") as client:
|
|
200
|
+
client.send_message("-1001234567890", "Build finished")
|
|
201
|
+
client.send_file("-1001234567890", "report.pdf", caption="Nightly report")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Errors derive from `TgPostError`: `AuthError`, `ForbiddenError`,
|
|
205
|
+
`BadRequestError`, `RateLimitError`, `ServerError`, `NetworkError`,
|
|
206
|
+
`FileTooLargeError`, `ConfigError` and `SchedulerError`. Retryable failures are
|
|
207
|
+
retried internally; what reaches you is a failure worth acting on.
|
|
208
|
+
|
|
209
|
+
## License
|
|
210
|
+
|
|
211
|
+
MIT. See [LICENSE](https://github.com/pandiyarajk/tgpost/blob/main/LICENSE).
|