Metadata-Version: 2.4
Name: tgpost
Version: 0.1.0
Summary: Send messages and files to Telegram channels, now or on a schedule
Project-URL: Repository, https://github.com/pandiyarajk/tgpost
Project-URL: Issues, https://github.com/pandiyarajk/tgpost/issues
Author-email: Pandiyaraj Karuppasamy <pandiyarajk@live.com>
License-Expression: MIT
License-File: LICENSE
Keywords: bot,cli,notifications,scheduler,telegram
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Chat
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: tzdata; platform_system == 'Windows'
Provides-Extra: dev
Requires-Dist: apscheduler<4,>=3.11; extra == 'dev'
Requires-Dist: build>=1.0; extra == 'dev'
Requires-Dist: mypy>=1.8; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Requires-Dist: sqlalchemy>=1.4; extra == 'dev'
Requires-Dist: twine>=4.0; extra == 'dev'
Provides-Extra: schedule
Requires-Dist: apscheduler<4,>=3.11; extra == 'schedule'
Requires-Dist: sqlalchemy>=1.4; extra == 'schedule'
Description-Content-Type: text/markdown

# tgpost

Send messages and files to Telegram channels, now or on a schedule.

---

> ### Disclaimer
>
> **Provided AS IS, without warranty of any kind, express or implied. Use
> entirely at your own risk.** The author accepts no liability for data loss,
> content published to the wrong channel or audience, business interruption or
> consequential damages.
>
> **You** are responsible for verifying the target channel, for what you publish,
> for being authorised to post there, and for complying with the Telegram Terms
> of Service. `tgpost schedule` and `tgpost daemon` publish **unattended**: a
> mistaken cron expression can post to a live channel repeatedly until someone
> stops it. Test against a private channel first.
>
> Not certified for regulated, forensic, safety-critical or high-assurance use.
> There is no delivery guarantee: the Bot API offers no idempotency key, so a
> send that times out may or may not have arrived.
>
> The [MIT LICENSE](https://github.com/pandiyarajk/tgpost/blob/main/LICENSE) is
> the governing text and prevails wherever this summary differs from it. See
> [DISCLAIMER.md](https://github.com/pandiyarajk/tgpost/blob/main/DISCLAIMER.md)
> for the full text.

---

## Why this exists

Sending a file to a Telegram channel from the command line is well covered
already. What is missing is doing it **later**, reliably.

The Telegram HTTP Bot API has no server-side scheduling: there is no
`schedule_date` parameter on any send method, so "post this at 9am on weekdays"
has to be solved on your side, and survive a reboot. tgpost is a sender with a
persistent job store attached, so a scheduled post still goes out after the
machine restarts.

If you only need to send something right now, `telegram-send` and `apprise` both
do that well.

## Install

```bash
pip install tgpost              # sending only
pip install tgpost[schedule]    # sending and scheduling
```

Requires Python 3.11 or newer.

## Getting started

Create a bot with [@BotFather](https://t.me/BotFather), then add it to your
channel **as an administrator with permission to post messages**. A bot cannot
post to a channel as an ordinary member.

```bash
export TGPOST_BOT_TOKEN="123456789:your-token-here"     # Windows: set TGPOST_BOT_TOKEN=...

tgpost targets add release-notes -1001234567890 --description "Build announcements"
tgpost check
```

`tgpost check` verifies the token and reports, per target, whether the bot can
actually post. Those two permission failures are the most common cause of a
send failing later, so it is worth running first.

### Finding a channel id

A public channel can be addressed as `@channelusername`. For a private channel,
post any message in it, then read the numeric id (it starts with `-100`):

```bash
tgpost targets add private-chan @temporary_username   # if public, then:
tgpost check                                          # prints the numeric id
```

## Sending

```bash
tgpost send --to release-notes --text "Build 1.2.3 shipped"
tgpost send --to release-notes --file build.zip --caption "Nightly build"
tgpost send --to release-notes --file a.png --file b.png --album --kind photo
git log --oneline -10 | tgpost send --to release-notes --stdin
tgpost send --to release-notes --text "check this first" --dry-run
```

Text longer than Telegram's 4096-character limit is split across several
messages at paragraph, line or word boundaries.

## Scheduling

```bash
tgpost schedule --to release-notes --text "Standup" --cron "0 9 * * 1-5" --tz Europe/London
tgpost schedule --to alerts --text "Still alive" --every 30m
tgpost schedule --to release-notes --file report.pdf --at "2026-09-10T14:00"
tgpost schedule --to alerts --text "in two hours" --at 2h

tgpost jobs          # what is scheduled, and when each next runs
tgpost history       # what actually happened
tgpost cancel a41a4c18
```

Jobs are stored in SQLite and survive a restart. Something has to run them:

```bash
tgpost daemon        # long-running: keeps firing jobs until stopped
tgpost run-due       # fires everything due, then exits
```

Use `daemon` on a machine that stays up. Use `run-due` from **Windows Task
Scheduler** or cron if you would rather not keep a process alive:

```
schtasks /create /tn "tgpost" /tr "tgpost run-due" /sc minute /mo 5
```

A job whose time passed while nothing was running still fires when the scheduler
comes back, as long as it is within the one-hour grace window. Missed repeats are
coalesced, so a weekend of downtime sends once rather than fifty times.

## Formatting

The default parse mode is **HTML**, because it only requires `&`, `<` and `>` to
be escaped. MarkdownV2 requires eighteen characters to be escaped, including
`.`, `-` and `!`, which appear in ordinary prose, and a single missed escape is
a hard error rather than a cosmetic one.

```bash
tgpost send --to alerts --text "<b>Deploy finished</b>"        # markup as written
tgpost send --to alerts --text "$RAW" --escape                 # escape untrusted text
tgpost send --to alerts --text "plain" --parse-mode none
```

Pass `--escape` for text you did not write yourself. Without it, the text is
sent as markup and a stray `<` will be rejected by Telegram.

## Configuration

Config lives at `%APPDATA%\tgpost\config.toml` on Windows, or
`~/.config/tgpost/config.toml` elsewhere.

```toml
[defaults]
parse_mode = "html"
base_url = "https://api.telegram.org"

[targets.release-notes]
chat_id = "-1001234567890"
description = "Build announcements"

[targets.alerts]
chat_id = "@my_public_channel"
parse_mode = "none"
```

| Variable | Purpose |
|---|---|
| `TGPOST_BOT_TOKEN` | Bot token. **Preferred over storing it in the config file.** |
| `TGPOST_CONFIG` | Path to the config file. |
| `TGPOST_DB` | Path to the job database. |

The token is read from `--token`, then `TGPOST_BOT_TOKEN`, then the config file.
`tgpost targets add` never writes the token to disk, so a token supplied on the
command line or in the environment is not persisted by accident.

## Limits

Set by the Bot API, not by this package:

| Limit | Value |
|---|---|
| Message text | 4096 characters (tgpost splits automatically) |
| Caption | 1024 characters |
| Photo upload | 10 MB |
| Any other file | 50 MB |
| Album | 2 to 10 items |
| Rate | About 1 message per second per chat, 30 per second overall |

tgpost paces sends to stay under the rate limits, and on a `429` waits exactly
the `retry_after` Telegram asks for rather than guessing.

To send files larger than 50 MB, run a
[local Bot API server](https://github.com/tdlib/telegram-bot-api), which raises
the limit to 2000 MB, and point tgpost at it:

```bash
tgpost --base-url http://127.0.0.1:8081 send --to release-notes --file big.iso
```

## Python API

```python
from tgpost import TelegramClient

with TelegramClient("123456789:your-token") as client:
    client.send_message("-1001234567890", "Build finished")
    client.send_file("-1001234567890", "report.pdf", caption="Nightly report")
```

Errors derive from `TgPostError`: `AuthError`, `ForbiddenError`,
`BadRequestError`, `RateLimitError`, `ServerError`, `NetworkError`,
`FileTooLargeError`, `ConfigError` and `SchedulerError`. Retryable failures are
retried internally; what reaches you is a failure worth acting on.

## License

MIT. See [LICENSE](https://github.com/pandiyarajk/tgpost/blob/main/LICENSE).
