{"id":16609529,"url":"https://github.com/dkorunic/e-dnevnik-bot","last_synced_at":"2026-03-02T13:19:44.586Z","repository":{"id":49386973,"uuid":"469807991","full_name":"dkorunic/e-dnevnik-bot","owner":"dkorunic","description":"e-Dnevnik grade/exam alerting system (https://ocjene.skole.hr)","archived":false,"fork":false,"pushed_at":"2026-02-23T09:49:45.000Z","size":958,"stargazers_count":58,"open_issues_count":0,"forks_count":8,"subscribers_count":7,"default_branch":"main","last_synced_at":"2026-02-23T16:48:22.596Z","etag":null,"topics":["carnet","e-dnevnik","golang","ocjene"],"latest_commit_sha":null,"homepage":"https://e-dnevnik.dkorunic.net/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dkorunic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-03-14T16:07:31.000Z","updated_at":"2026-02-23T09:49:49.000Z","dependencies_parsed_at":"2026-01-05T13:08:44.903Z","dependency_job_id":null,"html_url":"https://github.com/dkorunic/e-dnevnik-bot","commit_stats":{"total_commits":80,"total_committers":2,"mean_commits":40.0,"dds":0.0625,"last_synced_commit":"22ce309d08eb84f6d85305ebea99116fb8266709"},"previous_names":[],"tags_count":118,"template":false,"template_full_name":null,"purl":"pkg:github/dkorunic/e-dnevnik-bot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkorunic%2Fe-dnevnik-bot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkorunic%2Fe-dnevnik-bot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkorunic%2Fe-dnevnik-bot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkorunic%2Fe-dnevnik-bot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dkorunic","download_url":"https://codeload.github.com/dkorunic/e-dnevnik-bot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dkorunic%2Fe-dnevnik-bot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30003990,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T12:19:43.414Z","status":"ssl_error","status_checked_at":"2026-03-02T12:19:02.215Z","response_time":60,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["carnet","e-dnevnik","golang","ocjene"],"created_at":"2024-10-12T01:29:07.796Z","updated_at":"2026-03-02T13:19:44.576Z","avatar_url":"https://github.com/dkorunic.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"[![GitHub license](https://img.shields.io/github/license/dkorunic/e-dnevnik-bot)](https://github.com/dkorunic/e-dnevnik-bot/blob/master/LICENSE)\n[![GitHub release](https://img.shields.io/github/release/dkorunic/e-dnevnik-bot)](https://github.com/dkorunic/e-dnevnik-bot/releases/latest)\n[![Go Report Card](https://goreportcard.com/badge/github.com/dkorunic/e-dnevnik-bot)](https://goreportcard.com/report/github.com/dkorunic/e-dnevnik-bot)\n[![goreleaser](https://github.com/dkorunic/e-dnevnik-bot/actions/workflows/goreleaser.yml/badge.svg)](https://github.com/dkorunic/e-dnevnik-bot/actions/workflows/goreleaser.yml)\n\n![](gopher.png)\n\n## Table of Contents\n\n- [About](#about)\n- [Privacy policy](#privacy-policy)\n- [Requirements](#requirements)\n- [Installation](#installation)\n  - [Usage](#usage)\n  - [Configuration](#configuration)\n    - [User configuration](#user-configuration)\n    - [Telegram configuration](#telegram-configuration)\n    - [Discord configuration](#discord-configuration)\n    - [Slack configuration](#slack-configuration)\n    - [Mail/SMTP configuration](#mailsmtp-configuration)\n    - [WhatsApp configuration](#whatsapp-configuration)\n    - [Google Calendar configuration](#google-calendar-configuration)\n- [HOWTO](#howto)\n  - [Integration with Systemd](#integration-with-systemd)\n  - [Running as a Docker container](#running-as-a-docker-container)\n  - [Running using Docker Compose](#running-using-docker-compose)\n    - [Step 1: Define services in a Compose file](#step-1-define-services-in-a-compose-file)\n    - [Step 2: Create persistent directory and download configuration file](#step-2-create-persistent-directory-and-download-configuration-file)\n    - [Step 3: How to run and stop docker compose](#step-3-how-to-run-and-stop-docker-compose)\n  - [Running in Github Actions](#running-in-github-actions)\n- [Star History](#star-history)\n\n## About\n\ne-Dnevnik bot is a self-hosted alerting system that reads from the official [CARNet e-Dnevnik](https://ocjene.skole.hr/) portal and regularly polls for new information (e.g. new grades for all subjects, newly scheduled exams, etc.).\n\nThe 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:\n\n- [Discord](https://discord.com/)\n- [Telegram](https://telegram.org/)\n- [Slack](https://slack.com/)\n- [WhatsApp](https://www.whatsapp.com/)\n- Regular e-mail (e.g. Gmail SMTP, etc.)\n- [Google Calendar](https://calendar.google.com/) (exam events only)\n\nEach 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.\n\n**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.\n\n## Privacy policy\n\nThe bot uses Google's Application Programming Interface (API) Services to add school exam events to your Google Calendar.\n\nUse of information received from Google APIs complies with the [Google API Services User Data Policy](https://developers.google.com/terms/api-services-user-data-policy), including the [Limited Use requirements](https://support.google.com/cloud/answer/9110914#explain-types\u0026zippy=%2Ccould-you-explain-the-limited-use-requirements-from-the-google-api-services-user-data-policy).\n\nYour 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.\n\n## Requirements\n\nThe bot requires:\n\n- A working directory and a small amount of disk space for the database: approximately 1 MiB for ~50 grades,\n- AAI/AOSI credentials belonging to the skole.hr domain for e-Dnevnik,\n- One or more Discord, Telegram, Slack, WhatsApp, e-mail, or Google Calendar accounts/targets.\n\nThe bot runs on virtually any embedded device and any supported operating system, using approximately 20–25 MB of RSS memory during normal service operation.\n\n## Installation\n\nDownload the binary from the [releases](https://github.com/dkorunic/e-dnevnik-bot/releases) page along with the [example configuration file](https://raw.githubusercontent.com/dkorunic/e-dnevnik-bot/main/.e-dnevnik.toml.example).\n\n### Usage\n\n```shell\nNAME\n  e-dnevnik-bot\n\nFLAGS\n  -v, --verbose                verbose/debug log level\n  -0, --fulldebug              log every scraped event (only with verbose mode)\n  -d, --daemon                 enable daemon mode (running as a service)\n  -?, --help                   display help\n  -t, --test                   send a test event (to check if messaging works)\n  -l, --colorlogs              enable colorized console logs\n      --version                display program version\n      --readinglist            send reading list alerts\n  -j, --jitter BOOL            enable slight (up to 10%) jitter for tick intervals (default: true)\n  -f, --conffile STRING        configuration file (in TOML) (default: .e-dnevnik.toml)\n  -b, --database STRING        alert database file (default: .e-dnevnik.db)\n  -g, --calendartoken STRING   Google Calendar token file (default: calendar_token.json)\n  -c, --cpuprofile STRING      CPU profile output file\n  -m, --memprofile STRING      memory profile output file\n  -i, --interval DURATION      interval between polls when in daemon mode (default: 1h0m0s)\n  -p, --relevance DURATION     maximum relevance period for events (0 = unlimited) (default: 0s)\n  -r, --retries UINT           number of retry attempts on error (default: 3)\n```\n\nBy default, the bot runs from the current working directory and loads its [TOML](https://github.com/toml-lang/toml) configuration from `.e-dnevnik.toml`, or from the file specified with the `-f` flag.\n\nOther flags:\n\n- `-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`,\n- `-d`: enables daemon/service mode, where the bot runs continuously and wakes up at regular intervals (specified with `-i`); disabled by default,\n- `-f`: path to the configuration file containing usernames, passwords, and messaging service settings (in [TOML](https://github.com/toml-lang/toml) format),\n- `-i`: interval between polls in daemon/service mode (minimum 1h, default 1h),\n- `-r`: number of retry attempts on scraping or delivery failures (default: 3),\n- `-t`: sends a test message to all configured messaging services,\n- `-v`: enables verbose/debug logging for detailed insight into bot operation; disabled by default,\n- `-l`: enables colorized console logging with JSON output disabled,\n- `-g`: path to the Google Calendar API token file for reading and storing the OAuth2 token,\n- `-p`: maximum relevance period for non-exam events, to avoid sending alerts for retroactively changed entries,\n- `--version`: displays the program version,\n- `-j`: enables a ±10% random jitter on the poll interval,\n- `--readinglist`: enables processing and alerting on reading list events.\n\n### Configuration\n\nThe 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.\n\n#### User configuration\n\n```toml\n[[user]]\nusername = \"ime.prezime@skole.hr\"\npassword = \"lozinka\"\n```\n\nAs many `[[user]]` blocks as needed can be specified; all users are processed in parallel.\n\n#### Telegram configuration\n\n```toml\n[telegram]\ntoken = \"telegram_bot_token\"\nchatids = [ \"chat_id\", \"chat_id2\" ]\n```\n\nSteps required:\n\n1. Create a Telegram bot by following the official [Telegram bot guide](https://core.telegram.org/bots#3-how-do-i-create-a-bot), which involves messaging BotFather and completing a few simple steps.\n2. After creating the bot, message it from each Telegram account you want to receive notifications. Then retrieve the Chat IDs, typically via [https://api.telegram.org/botTOKEN/getUpdates](https://api.telegram.org/botTOKEN/getUpdates), replacing **TOKEN** with the bot token obtained in step 1.\n\n#### Discord configuration\n\n```toml\n[discord]\ntoken = \"discord_bot_token\"\nuserids = [ \"user_id\", \"user_id2\" ]\n```\n\nSteps required:\n\n1. Create a Discord bot by following the [Discord bot guide](https://discordpy.readthedocs.io/en/stable/discord.html). This involves both creating the bot and inviting it to your server.\n2. The only permission needed is **Send Messages**.\n3. User IDs can be found by [enabling](https://www.remote.tools/remote-work/how-to-find-discord-id) **Developer Mode** in your Discord client and then messaging your bot.\n\n#### Slack configuration\n\n```toml\n[slack]\ntoken = \"xoxb-slack_bot_token\"\nchatids = [ \"chat_id\", \"chat_id2\" ]\n```\n\nSteps required:\n\n1. Create a Slack bot by following the [official Slack bot guide](https://slack.com/help/articles/115005265703-Create-a-bot-for-your-workspace).\n2. The only OAuth scope required is **chat:write**.\n3. To find a member's Chat ID, click their username in Slack, select **View full profile**, then **Copy member ID**. A channel ID can also be used to send messages to a group channel.\n\n#### Mail/SMTP configuration\n\n```toml\n[mail]\nserver = \"smtp.gmail.com\"\nport = \"587\"\nusername = \"user.name@gmail.com\"\npassword = \"legacy_app_password\"\nfrom = \"user.name@gmail.com\"\nsubject = \"Nova ocjena iz e-Dnevnika\"\nto = [ \"user.name@gmail.com\", \"user2.name2@gmail.com\" ]\n```\n\nSteps required:\n\n1. For Gmail, follow the [Gmail Help Center guide](https://support.google.com/a/answer/176600?hl=en) to configure SMTP access. Other SMTP providers follow a similar, self-explanatory setup.\n\n#### WhatsApp configuration\n\n```toml\n[whatsapp]\nphonenumber = \"+385XXYYYYYYY\"\nuserids = [ \"+385XXYYYYYYY@s.whatsapp.net\", \"XXXYYYYYYY-ZZZZZZZZZZ@s.whatsapp.net\" ]\ngroups = [ \"group1\", \"group2\" ]\n```\n\nSteps required:\n\n1. Open the WhatsApp mobile application (iOS, Android, etc.).\n2. A phone number is required for automatic pairing via PIN. If no phone number is configured, pairing falls back to a QR code, which requires an interactive run on a desktop. During the initial sync, keep your Android/iOS WhatsApp application open and active for at least 2 minutes.\n3. Entries in `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.\n4. The WhatsApp session is stored in `.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.\n\n#### Google Calendar configuration\n\n```toml\n[calendar]\nname = \"Djeca ispiti\"\n```\n\nSteps required:\n\n1. Set up the Google Calendar API by following the [Go Quickstart guide](https://developers.google.com/calendar/api/quickstart/go#set_up_your_environment) to create a project, enable the Calendar API, and download `credentials.json` to the working directory.\n2. On the **first run**, the bot must be launched interactively in a terminal — it will open a browser for OAuth2 authorization and store the resulting token in `calendar_token.json` (configurable with `-g`). Subsequent runs use the cached token automatically.\n3. The `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.\n\n## HOWTO\n\n### Integration with Systemd\n\nFor a minimal [systemd](https://systemd.io/) setup running the service as user `ubuntu` inside `/home/ubuntu/e-dnevnik`, save the following file to `/etc/systemd/system/e-dnevnik.service`:\n\n```\n# /etc/systemd/system/e-dnevnik.service\n[Unit]\nDescription=e-Dnevnik daemon\nAfter=network-online.target\n\n[Service]\nType=notify\nWatchdogSec=30s\nWorkingDirectory=/home/ubuntu/e-dnevnik\nExecStart=/home/ubuntu/e-dnevnik/e-dnevnik-bot --daemon --verbose\n\nRestart=always\nRestartSec=2\nUser=ubuntu\nGroup=ubuntu\n\n[Install]\nWantedBy=multi-user.target\n```\n\nThen enable the service with the usual commands:\n\n```shell\nsystemctl daemon-reload\nsystemctl enable --now e-dnevnik\nsystemctl status e-dnevnik\n```\n\nNote: e-dnevnik-bot supports the systemd [sd_notify](https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html), `Type=notify`, and `WatchdogSec=` features from version **0.13.0** onwards. Earlier versions are only compatible with `Type=simple`.\n\n### Running as a Docker container\n\nUp-to-date images for Linux amd64/arm64/arm are available on [Docker Hub](https://hub.docker.com/r/dkorunic/e-dnevnik-bot). 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:\n\n```shell\ncd some/workdir\nmkdir ednevnik\n\ncurl https://raw.githubusercontent.com/dkorunic/e-dnevnik-bot/main/.e-dnevnik.toml.example \\\n    --output ednevnik/.e-dnevnik.toml\neditor ednevnik/.e-dnevnik.toml\n\ndocker pull dkorunic/e-dnevnik-bot\n\ndocker run --detach \\\n    --volume \"$(pwd)/ednevnik:/ednevnik\" \\\n    --restart unless-stopped \\\n    dkorunic/e-dnevnik-bot \\\n    --daemon \\\n    --verbose \\\n    --database /ednevnik/.e-dnevnik.db \\\n    --conffile /ednevnik/.e-dnevnik.toml\n```\n\n### Running using Docker Compose\n\nTo use `docker compose`, first ensure [Docker Compose is installed](https://docs.docker.com/compose/install/).\n\n#### Step 1: Define services in a Compose file\n\nCreate a `docker-compose.yml` file in your project directory (e.g. `~/docker-compose/ednevnik`):\n\n```bash\nuser@server:~/docker-compose$ mkdir ednevnik\nuser@server:~/docker-compose$ cd ednevnik\nuser@server:~/docker-compose$ editor docker-compose.yml\n```\n\nPaste the following into `docker-compose.yml`:\n\n```yaml\nversion: \"3\"\n# More info at https://github.com/dkorunic/e-dnevnik-bot\nservices:\n  ednevnik:\n    container_name: e-dnevnik\n    image: dkorunic/e-dnevnik-bot:latest\n    command:\n      - \"--daemon\"\n      - \"--database=/ednevnik/.e-dnevnik.db\"\n      - \"--conffile=/ednevnik/.e-dnevnik.toml\"\n    # Volumes store your data between container upgrades\n    volumes:\n      - ./ednevnik:/ednevnik\n    restart: unless-stopped\n```\n\n#### Step 2: Create persistent directory and download configuration file\n\nInside the project directory, create a directory called `ednevnik` for persistent storage, then download and edit the configuration file:\n\n```bash\nuser@server:~/docker-compose/ednevnik$ mkdir ednevnik\n\nuser@server:~/docker-compose/ednevnik$ curl https://raw.githubusercontent.com/dkorunic/e-dnevnik-bot/main/.e-dnevnik.toml.example \\\n    --output ednevnik/.e-dnevnik.toml\n\nuser@server:~/docker-compose/ednevnik$ editor ednevnik/.e-dnevnik.toml\n```\n\n#### Step 3: How to run and stop docker compose\n\nFrom the project directory where `docker-compose.yml` is located, start the service with:\n\n```bash\nuser@server:~/docker-compose/ednevnik$ docker compose up -d\n```\n\nThe `-d` / `--detach` flag runs the containers in the background.\n\nTo stop the service, run:\n\n```bash\nuser@server:~/docker-compose/ednevnik$ docker compose down\n```\n\n### Running in Github Actions\n\n\u003e **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.\n\nThis integration was originally created by Luka Kladaric [@allixsenos](https://twitter.com/allixsenos) — thanks, Luka! The original Gist is [here](https://gist.github.com/allixsenos/f12977de767f32450f435ec2f33b93f0); a copy is reproduced below:\n\n```yaml\nname: e-imenik run\n\n# Controls when the action will run.\non:\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n  # Run on push to any branch for testing\n  push:\n  # Run every 6 hours\n  schedule:\n    - cron: \"0 */6 * * *\"\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Render the config file\n        run: |\n          cat \u003e .e-dnevnik.toml \u003c\u003cEOF\n          [[user]]\n          username = \"$SKOLE_USERNAME\"\n          password = \"$SKOLE_PASSWORD\"\n\n          [telegram]\n          token = \"$TELEGRAM_TOKEN\"\n          chatids = [ \"$TELEGRAM_CHATID\" ]\n          EOF\n        env:\n          SKOLE_USERNAME: ${{ secrets.SKOLE_USERNAME }}\n          SKOLE_PASSWORD: ${{ secrets.SKOLE_PASSWORD }}\n          TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_TOKEN }}\n          TELEGRAM_CHATID: ${{ secrets.TELEGRAM_CHATID }}\n\n      - name: Run dkorunic/e-dnevnik-bot\n        run: |\n          docker run -t \\\n            --volume \"$(pwd):/ednevnik\" \\\n            --user $(id -u):$(id -g) \\\n            dkorunic/e-dnevnik-bot \\\n            --verbose \\\n            --database /ednevnik/.e-dnevnik.db \\\n            --conffile /ednevnik/.e-dnevnik.toml\n\n      - name: Delete the config file so it doesn't get committed back :)\n        run: rm .e-dnevnik.toml\n\n      - name: Commit the DB\n        uses: stefanzweifel/git-auto-commit-action@v4\n        with:\n          commit_author: GitHub Actions \u003cactions@github.com\u003e\n```\n\nThis 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.\n\n## Star History\n\n[![Star History Chart](https://api.star-history.com/svg?repos=dkorunic/e-dnevnik-bot\u0026type=Date)](https://star-history.com/#dkorunic/e-dnevnik-bot\u0026Date)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkorunic%2Fe-dnevnik-bot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdkorunic%2Fe-dnevnik-bot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdkorunic%2Fe-dnevnik-bot/lists"}