Telegram bot that organizes casual team games among friends: who is in, who is maybe, and the teams.
PartitelleBot keeps the participant list of the weekly match in a Telegram group and updates a single summary message as people join or drop out. When the list is full it splits the players into two random teams, and after the match it reminds the group to pay the pitch. The bot talks Italian; this README is in English.
It is a Python 3.12 project built on the async API of python-telegram-bot (v22). The bot runs on Render and stores its data in PostgreSQL on Neon (see docs/neon-db.md).
- Add @PartitelleBot to the group. Giving it administrator rights lets it pin its messages.
- Send
/startto create the match. By default it is the next Wednesday at 20:00 (today, if it is a Wednesday and 20:00 has not passed yet), with 10 players. Change it with/setday,/settimeand/setnumber. - Everybody writes plain chat keywords (see below) to join. The bot prints the summary the first time and then edits it in place;
/participantsshows it again on demand. - Once the confirmed players reach the target, the bot announces it and you can run
/teamsto get a black and a white team. - Teams can be adjusted with
scambia <name 1> con <name 2>. Removing a player or marking one as maybe revokes the teams (the bot says so);/teamsgenerates them again. - The day after the match, at no exact time, the bot asks the group whether everybody paid, and pins the message if it can. The reminder state is stored in the database, and a daily scheduled job calls an authenticated endpoint that sends it.
The summary looks like this (the pitch and the description are editable):
GIORNO: Mercoledì 04/11/2026 | 20:00
1. Anna ✅
2. Marco ❓
3. ❌
...
<description>
CAMPO:
<pitch>
✅ is confirmed, ❓ is maybe, ❌ is a free slot.
Keywords are plain messages, not commands. The message must start exactly with the keyword, and names are matched ignoring case.
aggiungi, proponi accept several comma-separated names (aggiungi luca, sara). The names are free text: they do not have to be Telegram users.
| message | effect |
|---|---|
aggiungimi |
add yourself, or confirm yourself if you were maybe |
aggiungi <name> |
add someone, or confirm them if they were maybe |
proponimi |
add yourself as maybe |
proponi <name> |
add someone as maybe |
toglimi |
remove yourself, whether confirmed or maybe |
togli <name> |
remove one person, whether confirmed or maybe |
scambia <name 1> con <name 2> |
swap two players of different teams (needs teams from /teams) |
/setday and /settime read a human date or time and refuse anything they cannot understand.
| command | description |
|---|---|
/start |
create the match of the chat (does nothing if one is already active) |
/setnumber <n> |
number of players, up to 40; teams need an even number. Revokes existing teams |
/setday <date> |
day of the match, day first, e.g. /setday 25/10/2026 |
/settime <time> |
time of the match, e.g. /settime 21:30, /settime 21.30 or /settime 20 |
/setdescription <text> |
text shown under the list; Telegram markdown is allowed |
/setpitch <name> |
pitch shown at the bottom of the summary |
/participants |
show the summary of the current match |
/teams |
generate the teams once the target is reached, or show the existing ones |
/stop |
stop the current match; it is kept as history and /start can create a new one |
/help |
list keywords and commands |
When the bot is configured with a Mini App (PB_MINIAPP_SHORT_NAME, see CONTRIBUTING.md), every summary carries an apri la lista button. It opens the list of that match inside Telegram, where members of the chat can join, propose themselves or leave, add or propose other people by name, remove someone from the list, draw the teams and swap two players between teams, with the same rules and the same effects on the group as the chat keywords (when someone acts on others, the group message says who did it). Every member of the chat can use every action. The link is private to the match: a new /start creates a new link and the old one stops working. Without PB_MINIAPP_SHORT_NAME the feature is off: no button and no /app routes.
Setup, workflow and conventions are in CONTRIBUTING.md.
- create your own bot with BotFather and copy its token
cp default.env .envand setPB_TG_TOKENto your tokendocker compose up -dstarts a local PostgreSQLuv syncinstalls the dependenciesuv run --env-file .env python -m db.migratecreates thePB_DB_SCHEMAschema and applies the migrations inmigrations/uv run --env-file .env python main.pystarts the bot (withENV=localit uses polling instead of a webhook)uv run pytestruns the tests
The .env file is not loaded automatically by the code: pass it with uv run --env-file .env as above (or export the variables in your shell).
Use docker compose down -v to also drop the local data.
| variable | meaning |
|---|---|
| ENV | local runs the bot in polling mode, any other value uses a webhook |
| PB_TG_TOKEN | Telegram bot token |
| PB_URL | public URL of the hosting, used for the webhook (not needed locally) |
| PB_VERSION | optional version shown by GET /health; defaults to the short RENDER_GIT_COMMIT |
| PB_JOBS_SECRET | optional bearer token for POST /jobs/payment-reminders; when unset or empty the endpoint is disabled (404) |
| PB_WEBHOOK_SECRET | optional secret Telegram sends with every webhook call ([A-Za-z0-9_-], 1-256 chars); when unset it is derived from PB_TG_TOKEN |
| PB_DB_SCHEMA | PostgreSQL schema of the environment: prod or staging (mandatory: the bot does not start without it) |
| PB_DB_HOST | PostgreSQL host |
| PB_DB_NAME | PostgreSQL database name |
| PB_DB_USER | PostgreSQL user |
| PB_DB_PASSWORD | PostgreSQL password |
| PB_DB_PORT | PostgreSQL port (also the host port published by docker compose) |
| PB_DB_SSLMODE | optional: libpq sslmode of every database connection, default require; set disable for the local docker compose database (already in default.env) |
| PB_DB_MIGRATE_USER, PB_DB_MIGRATE_PASSWORD | optional: credentials used only by python -m db.migrate, to run the bot with a role that cannot change the schema (see docs/neon-db.md); set both or neither, default PB_DB_USER/PB_DB_PASSWORD |
Nothing mandatory, just do it if you feel like offering a coffee: tap the Sponsor button at the top, or click here.
How partitelle handles your data: partitelle.com/privacy.
Released under the GNU Affero General Public License v3.0, see LICENSE. Versions up to 1.6.2 were released under the GNU General Public License v3.0.
The name "partitelle", the name "PartitelleBot" and the whistle mark are not covered by the license: see TRADEMARKS.md.
