| Command | Action |
|---|---|
| /list | Show all open tasks, numbered; reply with IDs to mark done |
| /list onlytext | Compact bullet list (no emojis or dates) β IDs only |
| /listtext | Same compact output as /list onlytext (no mark-done session) |
| /show <id> | Show full task detail and re-send any attachments |
| /search <query> | Find open tasks whose title or original text contains the query |
| /filter [category] | Filter open tasks by category (no arg β category picker buttons) |
| /history [days] | List tasks completed in the last N days (default 7), grouped by date |
| /done | Show numbered open task list; reply with IDs to mark done |
| /edit <id> | Bot asks what to change (free-text re-parse mode) |
| /edit <id> <field> <value> | Set a single field: title, category, date/due, priority |
| /defer <id> [days] | Push the due date later by N days (default 1) |
| /priority <id> [on|off] | Toggle β priority (no arg β toggle) |
| /snooze <id> <1h|30m|tomorrow|mon> | Push a reminder forward |
| /next | Suggest one task to do right now (skips if you're in a meeting) |
| /drop <id> | Delete a task permanently |
| /stats | Quick counts per category (open and done) |
| /sync | Pull any Notion changes into MariaDB on demand |
| /pushnotion [all] | Push every DB task missing a Notion page to Notion (open only by default) |
| /menu | Show a persistent reply keyboard with the most-used commands |
| /help | Print every command grouped by purpose |
| /cancel | Abort an in-progress /edit |
- Send any text to add a task β Claude parses it into title, category, due date, β priority, recurrence, and an optional reminder time.
- "every Monday", "jeden Dienstag", "monthly on the 15th", "every weekday", "daily" all create recurring tasks. When marked done, the next instance is auto-scheduled.
- "remind me at 3pm to call mum" or "tomorrow at 9 dentist" sets a one-shot reminder fired by the per-minute reminder cron.
- Pasting a URL by itself fetches the page title and uses it as the task name; the original URL is kept in
raw_text. - Forwarded messages are treated as captured items.
- Urgency keywords ("urgent", "asap", "wichtig", "dringend") set priority to true.
- Send a photo or document with a caption β the caption is parsed as the task text and the file is attached. The Telegram
file_idis stored; nothing is uploaded to your server or to Notion. Tasks with attachments show a π badge in/listand a checkbox in Notion (if the property exists). - Album uploads (multiple photos at once) are coalesced into a single task with multiple attachments.
- Photos/documents sent without a caption create a placeholder task you can rename via
/edit.
- Every freshly captured task comes with β Priority / π Today / π Tomorrow / β1d / βοΈ /edit / β Drop buttons.
- After
/done, an β©οΈ Undo button lets you re-open the most recently completed tasks. - New tasks parsed as
Unknowncome with quick-pick category buttons. /filterwith no argument shows a one-tap category picker.- Reminders sent by
reminder_check.pycome with β Done / π€ Snooze 1h / π Tomorrow AM buttons. - The Sunday weekly review surfaces stale tasks with β Drop / π Defer 30d / β Done buttons.
- The evening check-in surfaces open tasks due today with β Done / π Tomorrow / +1d buttons.
/nextsuggestion cards include a π Pick another button to cycle through candidates without repeating.
Assuming you clone the repository into ~/notetaker with
git clone https://github.com/PelzKo/notetaker.git- Open Telegram, search for @BotFather
- Send
/newbot, follow prompts, get your token - Start a chat with your new bot, then visit:
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesSend any message to the bot, refresh the URL, find"chat":{"id":XXXXXXX}β that's your TELEGRAM_CHAT_ID
- Go to https://console.anthropic.com
- Settings β API Keys β Create key
- Copy it, you won't see it again
mysql -u root -p
CREATE DATABASE konstip_notetaker CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'notetaker'@'localhost' IDENTIFIED BY 'choose_a_strong_password';
GRANT ALL PRIVILEGES ON notetaker.* TO 'notetaker'@'localhost';
FLUSH PRIVILEGES;
EXIT;This requires a one-time OAuth flow on your local machine (needs a browser), then copying the resulting token to the server.
- Go to https://console.cloud.google.com
- Create a new project (or reuse an existing one)
- Enable the Google Calendar API: APIs & Services β Library β search "Google Calendar API" β Enable
- Create credentials:
APIs & Services β Credentials β Create Credentials β OAuth client ID β Desktop app
β Download JSON β rename file to
credentials.json - Set up the OAuth consent screen: APIs & Services β OAuth consent screen β External β fill in any app name β Save and continue through the screens
- Add your Google account as a test user: OAuth consent screen β scroll to "Test users" β Add users β add your Gmail address β Save
- Publish the consent screen (important β otherwise refresh tokens expire every 7 days):
OAuth consent screen β "Publishing status" β click Publish App β confirm.
The
calendar.readonlyscope does not require Google verification for personal use; publishing here just stops the test-user refresh-token expiry.
pip install google-auth-oauthlib google-api-python-client
# Put credentials.json in the same directory as google_calendar.py, then:
python google_calendar.py
# A browser window opens β log in and allow access
# This creates token.json in the same directoryThe bot filters calendars by name. Check what names the API sees:
python - <<'EOF'
from google_calendar import _get_service
service = _get_service()
for cal in service.calendarList().list().execute().get("items", []):
print(repr(cal["summary"]))
EOFOpen google_calendar.py and update INCLUDED_CALENDARS to match exactly:
INCLUDED_CALENDARS = {"CALENDAR1", "CALENDAR2", "CALENDAR3"}scp credentials.json token.json user@yourserver:~/notetaker/token.json refreshes itself automatically β you will not need to redo the OAuth flow
unless you revoke access in your Google account. If you ever see "Calendar token expired
or revoked" in the daily summary, the consent screen is still in Testing mode (see step 7
above) or access was revoked β re-run the local OAuth flow and re-copy token.json.
Notion sync is completely optional. If you skip this section, the bot works exactly as before β just leave NOTION_API_KEY and NOTION_DATABASE_ID blank in .env.
When configured, every task you add/edit/complete/delete is mirrored to a Notion database in real time. You can also edit tasks directly in Notion and the changes are pulled back into MariaDB each morning (or on demand via /sync).
- Go to https://www.notion.so/my-integrations
- Click "+ New integration"
- Give it a name (e.g.
Notetaker Bot), select your workspace, leave type as Internal - Click Submit
- On the next screen, copy the "Installation access token" β this is your
NOTION_API_KEY. It starts withntn_and looks likentn_abc123...
Create a new full-page database in Notion (not an inline/embedded one β it must be its own page so it has its own URL). Add exactly these properties with these exact names and types:
| Property name | Type | Notes |
|---|---|---|
Name |
Title | Built-in, already exists |
Category |
Select | Add options: Work, Home, MCM, YFU, Personal, Other, Unknown |
Due Date |
Date | |
Done |
Checkbox | |
Done At |
Date | |
Task ID |
Number | Used to link Notion pages back to MariaDB rows |
Priority |
Checkbox | Optional. β flag synced both ways; bot skips it gracefully if absent |
Attachment |
Checkbox | Optional. Set automatically when a task has Telegram attachments (read-only, files stay in Telegram) |
Recurrence |
Rich Text | Optional. Shows the recurrence rule (e.g. weekly:mon, daily); read-only in Notion |
- Open the database as a full page in your browser (click the title, then "Open as full page" if needed)
- Look at the URL β it will look like one of:
https://www.notion.so/yourworkspace/abc1def2abc1def2abc1def2abc1def2?v=...https://www.notion.so/abc1def2-abc1-def2-abc1-def2abc1def2?v=...
- Copy the 32-character hex string before the
?v=(with or without dashes, both work) β that is yourNOTION_DATABASE_ID
NOTION_API_KEY=ntn_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NOTION_DATABASE_ID=abc1def2abc1def2abc1def2abc1def2
- MariaDB β Notion (real-time): every time you add, edit, complete, or delete a task via Telegram, the change is pushed to Notion immediately.
- Notion β MariaDB (daily + on-demand): each morning the scheduler pulls any changes made directly in Notion and applies them to MariaDB. Use
/syncin Telegram to trigger this manually at any time. - New pages in Notion: if you create a row directly in the Notion database (without a Task ID), it will be imported as a new task in MariaDB on the next sync.
- Conflict resolution: if a Notion page was edited more recently than the last sync watermark, Notion wins. Otherwise the MariaDB value is kept.
cd ~/notetaker
python3.11 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Configure
cp .env.example .env
chmod 600 .env
nano .env # fill in all valuescd ~/notetaker
set -a; source .env; set +a
# Test the bot
python bot.pySend a message to your bot in Telegram. If it responds, it works. Ctrl+C to stop.
# Test the daily summary (including calendar)
python scheduler.pyYou should receive the summary in Telegram. If the calendar block shows an error,
double-check that credentials.json and token.json are in ~/notetaker/.
crontab -eAdd these lines, replacing YOUR_LINUX_USER with your actual username:
# Keep bot alive (restart if down)
*/5 * * * * /home/YOUR_LINUX_USER/notetaker/keepalive.sh >> /home/YOUR_LINUX_USER/notetaker/logs/keepalive.log 2>&1
# Morning summary at 08:00 (Notion sync + calendar + task digest + interesting reads)
0 8 * * * set -a; source /home/YOUR_LINUX_USER/notetaker/.env; set +a; /home/YOUR_LINUX_USER/notetaker/venv/bin/python /home/YOUR_LINUX_USER/notetaker/scheduler.py >> /home/YOUR_LINUX_USER/notetaker/logs/notetaker_cron.log 2>&1
# Per-minute reminder check
* * * * * set -a; source /home/YOUR_LINUX_USER/notetaker/.env; set +a; /home/YOUR_LINUX_USER/notetaker/venv/bin/python /home/YOUR_LINUX_USER/notetaker/reminder_check.py >> /home/YOUR_LINUX_USER/notetaker/logs/reminders.log 2>&1
# Evening check-in at 20:00 (today's done, open tasks, tomorrow's calendar, auto-prep tasks)
0 20 * * * set -a; source /home/YOUR_LINUX_USER/notetaker/.env; set +a; /home/YOUR_LINUX_USER/notetaker/venv/bin/python /home/YOUR_LINUX_USER/notetaker/evening.py >> /home/YOUR_LINUX_USER/notetaker/logs/evening.log 2>&1
# Sunday 18:00 weekly review
0 18 * * 0 set -a; source /home/YOUR_LINUX_USER/notetaker/.env; set +a; /home/YOUR_LINUX_USER/notetaker/venv/bin/python /home/YOUR_LINUX_USER/notetaker/weekly_review.py >> /home/YOUR_LINUX_USER/notetaker/logs/weekly.log 2>&1
# Check bot is running
pgrep -f "bot.py" # should print a PID
tail -f /home/YOUR_LINUX_USER/notetaker/logs/notetaker.log # should show "Bot startingβ¦"Then in Telegram:
- Send a task:
fix HIPPIE migration bug by Fridayβ Bot replies with parsed title, category (Work), due date - Send
/listβ task appears numbered - Reply with
1β task marked done - Send
/statsβ category counts - Run
python ~/notetaker/scheduler.pymanually β summary arrives with calendar block
The */5 * * * * cron entry handles restarts automatically β if the server reboots,
the bot will be back within 5 minutes with no action needed on your part.
Check at any time:
pgrep -f "bot.py" && echo "running" || echo "not running"