# Ofek-bot
Ofek-bot is a Python bot that logs in to the **Ofek** learning website by CET, used in Israeli
schools ([myofek.cet.ac.il](https://myofek.cet.ac.il/he)), on behalf of each of your kids.
It signs in through the Ministry of Education's SSO (edu.gov.il), reads their task counters, and sends you a notification through
[Apprise](https://github.com/caronc/apprise) when a kid has tasks to complete or fix. It runs
on a daily schedule inside a Docker container with a headless Chrome browser driven by
Selenium.
> [!IMPORTANT]
> **Unofficial project.** Ofek-bot is not affiliated with, endorsed by, or supported by Ofek,
> CET, the Israeli Ministry of Education, or any school. It automates a normal browser login
> with credentials you provide. You are responsible for making sure your use complies with the
> website's terms of use and your school's policies.
## Table of contents
- [Features](#features)
- [How it works](#how-it-works)
- [Requirements](#requirements)
- [Installation](#installation)
- [Configuration](#configuration)
- [Notifications](#notifications)
- [Logging](#logging)
- [Troubleshooting](#troubleshooting)
- [Security and privacy](#security-and-privacy)
- [Known issues and limitations](#known-issues-and-limitations)
- [Development](#development)
- [Contributing](#contributing)
- [License](#license)
## Features
- Monitors the Ofek task status of any number of kids, each with their own login.
- Logs in through the edu.gov.il SSO using the username/password option.
- Reads four counters from the Ofek dashboard: tasks to do, tasks to fix, checked tasks, and
tasks waiting for review.
- Notifies only when there is something to do (the "to do" or "to fix" counter is above zero).
- Runs on one or more daily schedules (default: 16:00).
- Supports many notification channels (Telegram, Discord, Slack, email, Pushover, Home
Assistant, and more), thanks to [Apprise](https://github.com/caronc/apprise).
### Components and frameworks
- [Loguru](https://pypi.org/project/loguru/) for logging.
- [Schedule](https://pypi.org/project/schedule/) for the run schedule.
- [Apprise](https://github.com/caronc/apprise) for notifications.
- [Selenium](https://selenium-python.readthedocs.io/) and
[webdriver-manager](https://pypi.org/project/webdriver-manager/) for browser automation.
- [PyYAML](https://pypi.org/project/PyYAML/) for the configuration file.
## How it works
```mermaid
flowchart TD
A[Start: load config/config.yaml and NOTIFIERS / SCHEDULES] --> B{Scheduled time?}
B -- no --> B
B -- yes --> C[For each kid in config]
C --> V{name, username and password all set?}
V -- no --> X[Log warning and stop this run]
V -- yes --> D[Start headless Chrome]
D --> E[Open myofek.cet.ac.il/he and click Login]
E --> F[Choose edu.gov.il SSO, switch to username/password login]
F --> G[Enter the kid's username and password]
G --> H[Read the four task counters from the dashboard]
H --> I{To do > 0 or to fix > 0?}
I -- yes --> J[Send Apprise notification]
I -- no --> K[Log: no tasks]
J --> L[Close browser]
K --> L
L --> C
```
The diagram shows the normal path. If a scrape fails, the error is logged and the previous
counter values are kept. If no counter has been read since the bot started, the counter check
raises an error that ends the run for all remaining kids (see
[Known issues and limitations](#known-issues-and-limitations)).
1. On startup the bot loads the kids list from `config/config.yaml` (relative to the working
directory, `/app` in the container). If the file does not exist, it copies the empty
template `config.yaml` there. The file is read **once**, so restart the bot after you
change it.
2. It registers every Apprise URL from `NOTIFIERS` and one daily job for every time in
`SCHEDULES`.
3. At each scheduled time, for every kid, it starts a fresh headless Chrome session
(incognito, `--no-sandbox`, `--disable-dev-shm-usage`), logs in to Ofek through the
edu.gov.il SSO, and reads the text of the four counters on the dashboard.
4. It takes the first number in the "to do" and "to fix" counters. If either is greater than
zero, it sends a notification; otherwise it logs that there are no tasks.
There is no stored state or de-duplication: as long as a kid has open tasks, you get a
notification at **every** scheduled run.
## Requirements
- Docker (recommended), **or** Python 3 with Google Chrome installed locally.
- Outbound internet access to `myofek.cet.ac.il`, the edu.gov.il login pages, and your
notification services. `webdriver-manager` also downloads a matching ChromeDriver at run
time, so it needs internet access too.
- An Ofek (edu.gov.il) **username and password** for each kid. The bot uses the
username/password login, not the SMS login.
- At least one [Apprise URL](https://github.com/caronc/apprise/wiki) for notifications.
## Installation
### Docker Compose (recommended)
```yaml
services:
ofek:
image: techblog/ofek-bot:latest
container_name: ofek
restart: always
environment:
- SCHEDULES=16:00
- NOTIFIERS=tgram:///
volumes:
- ./ofek-bot/config:/app/config
```
1. Create the config folder and the `config.yaml` file described in
[Configuration](#configuration):
```bash
mkdir -p ofek-bot/config
nano ofek-bot/config/config.yaml
```
2. Start the container:
```bash
docker compose up -d
docker compose logs -f ofek
```
### Docker
```bash
docker run -d --name ofek --restart always \
-e SCHEDULES="07:30,16:00" \
-e NOTIFIERS="tgram:///" \
-v "$(pwd)/ofek-bot/config:/app/config" \
techblog/ofek-bot:latest
```
### Published images
Images are published to Docker Hub as
[`techblog/ofek-bot`](https://hub.docker.com/r/techblog/ofek-bot) for `linux/amd64` only.
The tags are `latest` and the version from the [`VERSION`](VERSION) file. There are no GitHub
Releases.
> [!NOTE]
> At the time of writing, the newest tag on Docker Hub is `3.2.1` (September 2024), which is
> also `latest`. The current `VERSION` file says `3.3.1`, and the Dockerfile on `main` (based
> on `selenium/standalone-chrome`) has not been published yet. Until a new image is built,
> `latest` is built from an older Dockerfile.
### From source
```bash
git clone https://github.com/t0mer/ofek-bot.git
cd ofek-bot
pip3 install -r requirements.txt
cd app # the bot reads config/config.yaml relative to this folder
mkdir -p config
cp config.yaml config/config.yaml
nano config/config.yaml # add your kids
export NOTIFIERS="tgram:///" # must be set, even to ""
export SCHEDULES="16:00"
python3 app.py
```
Google Chrome must be installed; `webdriver-manager` downloads the matching ChromeDriver.
## Configuration
Configuration comes from two environment variables and one YAML file. There are no command
line flags.
### Environment variables
| Variable | Required | Default | Description |
| -------- | -------- | ------- | ----------- |
| `NOTIFIERS` | Yes, to get notifications | `""` in the Docker image; unset outside Docker | One or more [Apprise URLs](#notifications), separated by **spaces**. If empty, the bot still checks Ofek but sends nothing. Outside Docker it must be set (even to an empty string), or the bot exits on startup. |
| `SCHEDULES` | No | `16:00` | Daily run times in 24-hour `HH:MM` format, separated by commas **without spaces**, for example `07:30,16:00`. Times use the container's local time zone. |
| `TZ` | No | `UTC` (from the `selenium/standalone-chrome` base image) | Time zone for `SCHEDULES`, for example `Asia/Jerusalem`. The `selenium/standalone-chrome` base image supports it; the bot itself does not read it. |
### `config/config.yaml`
The kids list is stored in `/app/config/config.yaml` inside the container. Mount a host folder
to `/app/config` so the file survives container updates.
| Key | Required | Description |
| --- | -------- | ----------- |
| `kids` | Yes | List of kids to check. |
| `kids[].name` | Yes | Display name. Used in the notification title and the logs only. |
| `kids[].username` | Yes | The kid's edu.gov.il username (usually the ID number). Numbers are converted to strings. |
| `kids[].password` | Yes | The kid's edu.gov.il password. |
Example:
```yaml
kids:
- name: Kid1
username: "000000000"
password: ""
- name: Kid2
username: "000000000"
password: ""
```
Quote usernames with leading zeros (`"012345678"`), or YAML may misread them as numbers
(including octal) and change the value.
All three keys are required for every entry. When the bot reaches an entry with an empty
`name`, `username`, or `password`, it logs `Kids list is empty or not configured` and **stops
processing the rest of the list** for that run. Remove unused entries instead of leaving them
blank.
## Notifications
Set `NOTIFIERS` to one or more Apprise URLs separated by spaces:
```bash
NOTIFIERS="tgram:/// pover://@"
```
A few common examples (see the [Apprise wiki](https://github.com/caronc/apprise/wiki) for the
full, current list of services and URL formats):
| Service | Example URL |
| ------- | ----------- |
| [Telegram](https://github.com/caronc/apprise/wiki/Notify_telegram) | `tgram://bottoken/ChatID` |
| [Discord](https://github.com/caronc/apprise/wiki/Notify_discord) | `discord://webhook_id/webhook_token` |
| [Slack](https://github.com/caronc/apprise/wiki/Notify_slack) | `slack://TokenA/TokenB/TokenC/Channel` |
| [Pushover](https://github.com/caronc/apprise/wiki/Notify_pushover) | `pover://user@token` |
| [Gotify](https://github.com/caronc/apprise/wiki/Notify_gotify) | `gotifys://hostname/token` |
| [ntfy](https://github.com/caronc/apprise/wiki/Notify_ntfy) | `ntfys://hostname/topic` |
| [Home Assistant](https://github.com/caronc/apprise/wiki/Notify_homeassistant) | `hassio://hostname/accesstoken` |
| [Microsoft Teams](https://github.com/caronc/apprise/wiki/Notify_msteams) | `msteams://TokenA/TokenB/TokenC/` |
| [MQTT](https://github.com/caronc/apprise/wiki/Notify_mqtt) | `mqtt://user:pass@hostname/topic` |
| [Email](https://github.com/caronc/apprise/wiki/Notify_email) | `mailtos://user:password@gmail.com` |
| [Apprise API](https://github.com/caronc/apprise/wiki/Notify_apprise_api) | `apprises://hostname/Token` |
### Message format
A notification is sent per kid, only when the "to do" or "to fix" counter is above zero.
- **Title:** `מצב משימות אופק של ` ("Ofek task status of <name>").
- **Body:** the text of the four dashboard counters exactly as Ofek shows them (in Hebrew),
one per line, in this order: to do, to fix, checked, waiting.
## Logging
The bot logs to standard error with Loguru's default format at `DEBUG` level. The level is
not configurable. View the logs with `docker logs -f ofek`.
Useful messages: `Loading kids list`, `Getting tasks for: `, `Scrapping...`,
`No tasks tbd for `, `Setting default schedule to 16:00` (when `SCHEDULES` is empty),
and `Setting schedule to everyday at