
e-Dnevnik bot is a self-hosted alerting system that reads from the official CARNet e-Dnevnik portal and regularly polls for new information (e.g. new grades for all subjects, newly scheduled exams, etc.).
The bot can log in as multiple AAI/AOSI users from the skole.hr domain and check for new information for all of them, either as a one-shot run or as a long-running service that polls at regular intervals (e.g. hourly). All new, previously unseen events will trigger an alert. The bot can deliver alerts through the following messaging systems and services:
Each alert can be broadcast through multiple services simultaneously, and each service can have multiple recipients. All authentication credentials remain exclusively on your PC or server.
Important note (May 2023): e-Dnevnik bot will not be able to fetch data unless it is hosted inside Croatia, as CARNet has implemented a firewall that blocks access from non-Croatian IP addresses. This affects all popular cloud VM and VPS providers such as Oracle Cloud, Contabo, AWS, Azure, and GitHub Actions. Self-hosting at home or in a local office is strongly recommended.
The bot uses Google’s Application Programming Interface (API) Services to add school exam events to your Google Calendar.
Use of information received from Google APIs complies with the Google API Services User Data Policy, including the Limited Use requirements.
Your Google information is used solely to provide the user-facing calendar integration feature. No data is collected, stored remotely, or tracked in any way. All authorization data (e.g. the Google API token) is stored locally alongside the application and never leaves your premises.
The bot requires:
The bot runs on virtually any embedded device and any supported operating system, using approximately 20–25 MB of RSS memory during normal service operation.
Download the binary from the releases page along with the example configuration file.
NAME
e-dnevnik-bot
FLAGS
-v, --verbose verbose/debug log level
-0, --fulldebug log every scraped event (only with verbose mode)
-d, --daemon enable daemon mode (running as a service)
-?, --help display help
-t, --test send a test event (to check if messaging works)
-l, --colorlogs enable colorized console logs
--version display program version
--readinglist send reading list alerts
-j, --jitter BOOL enable slight (up to 10%) jitter for tick intervals (default: true)
-f, --conffile STRING configuration file (in TOML) (default: .e-dnevnik.toml)
-b, --database STRING alert database file (default: .e-dnevnik.db)
-g, --calendartoken STRING Google Calendar token file (default: calendar_token.json)
-c, --cpuprofile STRING CPU profile output file
-m, --memprofile STRING memory profile output file
-i, --interval DURATION interval between polls when in daemon mode (default: 1h0m0s)
-p, --relevance DURATION maximum relevance period for events (0 = unlimited) (default: 0s)
-r, --retries UINT number of retry attempts on error (default: 3)
By default, the bot runs from the current working directory and loads its TOML configuration from .e-dnevnik.toml, or from the file specified with the -f flag.
Other flags:
-b: path to the alert database file used to track seen alerts (default: .e-dnevnik.db); note that the .sqlite extension is appended automatically, so the actual file on disk will be .e-dnevnik.db.sqlite,-d: enables daemon/service mode, where the bot runs continuously and wakes up at regular intervals (specified with -i); disabled by default,-f: path to the configuration file containing usernames, passwords, and messaging service settings (in TOML format),-i: interval between polls in daemon/service mode (minimum 1h, default 1h),-r: number of retry attempts on scraping or delivery failures (default: 3),-t: sends a test message to all configured messaging services,-v: enables verbose/debug logging for detailed insight into bot operation; disabled by default,-l: enables colorized console logging with JSON output disabled,-g: path to the Google Calendar API token file for reading and storing the OAuth2 token,-p: maximum relevance period for non-exam events, to avoid sending alerts for retroactively changed entries,--version: displays the program version,-j: enables a ±10% random jitter on the poll interval,--readinglist: enables processing and alerting on reading list events.The configuration file consists of several blocks. The user block can be repeated as many times as needed. The Telegram, Discord, Slack, and e-mail blocks can each appear only once, but all can be enabled or disabled independently. Recipient lists (user IDs, chat IDs, and to addresses) are defined as arrays and support any number of entries. Alerts are broadcast to all enabled messaging services simultaneously.
[[user]]
username = "ime.prezime@skole.hr"
password = "lozinka"
As many [[user]] blocks as needed can be specified; all users are processed in parallel.
[telegram]
token = "telegram_bot_token"
chatids = [ "chat_id", "chat_id2" ]
Steps required:
[discord]
token = "discord_bot_token"
userids = [ "user_id", "user_id2" ]
Steps required:
[slack]
token = "xoxb-slack_bot_token"
chatids = [ "chat_id", "chat_id2" ]
Steps required:
[mail]
server = "smtp.gmail.com"
port = "587"
username = "user.name@gmail.com"
password = "legacy_app_password"
from = "user.name@gmail.com"
subject = "Nova ocjena iz e-Dnevnika"
to = [ "user.name@gmail.com", "user2.name2@gmail.com" ]
Steps required:
[whatsapp]
phonenumber = "+385XXYYYYYYY"
userids = [ "+385XXYYYYYYY@s.whatsapp.net", "XXXYYYYYYY-ZZZZZZZZZZ@s.whatsapp.net" ]
groups = [ "group1", "group2" ]
Steps required:
userids are either a personal JID (in the form +385XXYYYYYYY@s.whatsapp.net, for direct messages) or a group chat JID (in the form XXXYYYYYYY-ZZZZZZZZZZ@s.whatsapp.net). If you do not know a group’s JID, you can specify groups by name in the groups field; the bot will print the group’s JID in a debug message. Using userids directly is preferred for performance reasons..e-dnevnik.wa.sqlite in the working directory. When running in Docker, include this file in your persistent volume mount so that the pairing survives container restarts.[calendar]
name = "Djeca ispiti"
Steps required:
credentials.json to the working directory.calendar_token.json (configurable with -g). Subsequent runs use the cached token automatically.name field specifies the target Google Calendar by name (e.g. a calendar you created called Djeca ispiti). Only exam events are added as calendar entries.For a minimal systemd setup running the service as user ubuntu inside /home/ubuntu/e-dnevnik, save the following file to /etc/systemd/system/e-dnevnik.service:
# /etc/systemd/system/e-dnevnik.service
[Unit]
Description=e-Dnevnik daemon
After=network-online.target
[Service]
Type=notify
WatchdogSec=30s
WorkingDirectory=/home/ubuntu/e-dnevnik
ExecStart=/home/ubuntu/e-dnevnik/e-dnevnik-bot --daemon --verbose
Restart=always
RestartSec=2
User=ubuntu
Group=ubuntu
[Install]
WantedBy=multi-user.target
Then enable the service with the usual commands:
systemctl daemon-reload
systemctl enable --now e-dnevnik
systemctl status e-dnevnik
Note: e-dnevnik-bot supports the systemd sd_notify, Type=notify, and WatchdogSec= features from version 0.13.0 onwards. Earlier versions are only compatible with Type=simple.
Up-to-date images for Linux amd64/arm64/arm are available on Docker Hub. To set this up, create a persistent directory named ednevnik in your working directory, place the configuration file there, and mount it into the container:
cd some/workdir
mkdir ednevnik
curl https://raw.githubusercontent.com/dkorunic/e-dnevnik-bot/main/.e-dnevnik.toml.example \
--output ednevnik/.e-dnevnik.toml
editor ednevnik/.e-dnevnik.toml
docker pull dkorunic/e-dnevnik-bot
docker run --detach \
--volume "$(pwd)/ednevnik:/ednevnik" \
--restart unless-stopped \
dkorunic/e-dnevnik-bot \
--daemon \
--verbose \
--database /ednevnik/.e-dnevnik.db \
--conffile /ednevnik/.e-dnevnik.toml
To use docker compose, first ensure Docker Compose is installed.
Create a docker-compose.yml file in your project directory (e.g. ~/docker-compose/ednevnik):
user@server:~/docker-compose$ mkdir ednevnik
user@server:~/docker-compose$ cd ednevnik
user@server:~/docker-compose$ editor docker-compose.yml
Paste the following into docker-compose.yml:
version: "3"
# More info at https://github.com/dkorunic/e-dnevnik-bot
services:
ednevnik:
container_name: e-dnevnik
image: dkorunic/e-dnevnik-bot:latest
command:
- "--daemon"
- "--database=/ednevnik/.e-dnevnik.db"
- "--conffile=/ednevnik/.e-dnevnik.toml"
# Volumes store your data between container upgrades
volumes:
- ./ednevnik:/ednevnik
restart: unless-stopped
Inside the project directory, create a directory called ednevnik for persistent storage, then download and edit the configuration file:
user@server:~/docker-compose/ednevnik$ mkdir ednevnik
user@server:~/docker-compose/ednevnik$ curl https://raw.githubusercontent.com/dkorunic/e-dnevnik-bot/main/.e-dnevnik.toml.example \
--output ednevnik/.e-dnevnik.toml
user@server:~/docker-compose/ednevnik$ editor ednevnik/.e-dnevnik.toml
From the project directory where docker-compose.yml is located, start the service with:
user@server:~/docker-compose/ednevnik$ docker compose up -d
The -d / --detach flag runs the containers in the background.
To stop the service, run:
user@server:~/docker-compose/ednevnik$ docker compose down
Warning: CARNet has implemented a firewall that blocks access to e-Dnevnik from non-Croatian IP addresses. GitHub Actions runners use IP addresses outside Croatia, so this integration will fail to scrape data. It is kept here for reference only. For reliable operation, self-host the bot on hardware located in Croatia.
This integration was originally created by Luka Kladaric @allixsenos — thanks, Luka! The original Gist is here; a copy is reproduced below:
name: e-imenik run
# Controls when the action will run.
on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Run on push to any branch for testing
push:
# Run every 6 hours
schedule:
- cron: "0 */6 * * *"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Render the config file
run: |
cat > .e-dnevnik.toml <<EOF
[[user]]
username = "$SKOLE_USERNAME"
password = "$SKOLE_PASSWORD"
[telegram]
token = "$TELEGRAM_TOKEN"
chatids = [ "$TELEGRAM_CHATID" ]
EOF
env:
SKOLE_USERNAME: $
SKOLE_PASSWORD: $
TELEGRAM_TOKEN: $
TELEGRAM_CHATID: $
- name: Run dkorunic/e-dnevnik-bot
run: |
docker run -t \
--volume "$(pwd):/ednevnik" \
--user $(id -u):$(id -g) \
dkorunic/e-dnevnik-bot \
--verbose \
--database /ednevnik/.e-dnevnik.db \
--conffile /ednevnik/.e-dnevnik.toml
- name: Delete the config file so it doesn't get committed back :)
run: rm .e-dnevnik.toml
- name: Commit the DB
uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_author: GitHub Actions <actions@github.com>
This GitHub Actions workflow runs every 6 hours. On each run it checks out the repository (which includes the database), renders the configuration file from secrets, runs the bot, and commits the updated database back to the repository, providing persistence across runs.