├── requirements.txt ├── docker-compose.yml ├── crontab ├── Dockerfile ├── notify.sh ├── LICENSE ├── README.md ├── ausweisstatus.py └── .gitignore /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | bs4 3 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | --- 2 | services: 3 | ausweisstatus: 4 | container_name: ausweisstatus 5 | build: . 6 | -------------------------------------------------------------------------------- /crontab: -------------------------------------------------------------------------------- 1 | # perso 2 | */15 * * * * cd /ausweisstatus && WEBHOOK_URL="url_here" ./notify.sh P L7AAAAAAAA 01.01.1970 >> /var/log/L7 2>&1 3 | # reisepass 4 | #*/15 * * * * cd /ausweisstatus && WEBHOOK_URL="url_here" ./notify.sh R C7AAAAAAAA 01.01.1970 >> /var/log/C7 2>&1 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | 3 | RUN apk add --no-cache bash curl py-pip 4 | 5 | COPY . /ausweisstatus 6 | WORKDIR /ausweisstatus 7 | 8 | RUN python3 -m venv .venv 9 | RUN source .venv/bin/activate && pip3 install -r requirements.txt 10 | 11 | COPY crontab /etc/crontabs/root 12 | 13 | CMD ["crond", "-f"] 14 | -------------------------------------------------------------------------------- /notify.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script runs ausweisstatus.py with the provided arguments sends a notification to a discord webhook if the 4 | # status has changed since the last execution 5 | 6 | # Usage: ./notify.sh 7 | # also set WEBHOOK_URL env var 8 | 9 | notify() { 10 | message="New status for $2 ($3): $1" 11 | echo "$message" 12 | curl -q -i -H "Accept: application/json" -H "Content-Type:application/json" -X POST --data "{\"content\": \"$message\"}" "$WEBHOOK_URL" &>/dev/null 13 | } 14 | 15 | # source venv if found 16 | test -e venv/bin/activate && source venv/bin/activate 17 | test -e .venv/bin/activate && source .venv/bin/activate 18 | 19 | output="$(./ausweisstatus.py "$@")" 20 | 21 | if [ $? != 0 ]; then 22 | echo "$output" 23 | exit 1 24 | fi 25 | 26 | if [[ -e ./cache/"$2" ]]; then 27 | read -r previous < ./cache/"$2" 28 | if [ "$previous" != "$output" ]; then 29 | notify "$output" "$2" "$1" 30 | else 31 | echo "No status change for $2" 32 | fi 33 | else 34 | echo "No previous state found, caching current status for $2" 35 | fi 36 | 37 | mkdir -p ./cache 38 | echo "$output" > ./cache/"$2" 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ausweisstatus 2 | 3 | A small application to check the status of IDs like _Personalausweis_ and _Reisepass_ on https://ausweisstatus.regioit.de/aachen automatically and send notifications on changes. 4 | 5 | Tested with the status page for Aachen but _should_ work for other cities using _regioit.de_ 6 | 7 | ## Why? 8 | I wanted to know when my _Personalausweis_ was ready for collection without manually checking the page periodically. 9 | 10 | ## Usage 11 | 12 | ### With Docker 13 | A `Dockerfile` is provided containing everything necessary to send Discord notifications on status changes. 14 | 15 | Before building the Docker image, adjust the `WEBHOOK_URL` (see [here](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) for creation instructions), _Passauswahl_ (_P_, _R_, or _ID_, see below), _Seriennummer_ and _Geburtsdatum_ in the `crontab` file. 16 | 17 | Then, build and start the container with `docker compose up -d --build` on a machine with high availability. 18 | You will now be notified when the status of your document is changed. 19 | 20 | ### Without Docker 21 | First, install the dependencies, I recommend using [`uv`](https://docs.astral.sh/uv/): 22 | ```bash 23 | uv venv 24 | source .venv/bin/activate 25 | uv pip install -r requirements.txt 26 | ``` 27 | The provided script `ausweisstatus.py` can be used to retrieve the current status of your document. 28 | 29 | ``` 30 | ./ausweisstatus.py 31 | ``` 32 | 33 | - _P_ for _Personalausweis_ 34 | - _R_ for _Reisepass_ 35 | - _ID_ for eID-Karte 36 | 37 | Example: 38 | ```bash 39 | $ ./ausweisstatus.py P L712345678 01.01.1970 40 | bei Bundesdruckerei erfasst 41 | ``` 42 | 43 | To automatically receive notifications on status changes, you can periodically run the `notify.sh` script (requires _curl_) with the same arguments and a `WEBHOOK_URL` env var containing a Discord webhook URL. 44 | You can modify the `notify()` function in it to use other notification services. 45 | 46 | To run the script periodically, you can use [_systemd/Timers_](https://wiki.archlinux.org/title/Systemd/Timers) or [_cron_](https://wiki.archlinux.org/title/Cron). 47 | 48 | ## Note for other Cities 49 | The URL https://ausweisstatus.regioit.de/aachen is hard-coded in `ausweisstatus.py`, make sure to change it. -------------------------------------------------------------------------------- /ausweisstatus.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import requests 4 | from bs4 import BeautifulSoup 5 | import sys 6 | 7 | # parse args 8 | if len(sys.argv) != 4: 9 | print(f"Usage: {sys.argv[0]} \nP: Personalausweis\nR: Reisepass\nID: eID-Karte", file=sys.stderr) 10 | exit(1) 11 | 12 | passauswahl = None 13 | match sys.argv[1]: 14 | case "P": 15 | passauswahl = "B" 16 | case "R": 17 | passauswahl = "R" 18 | case "ID": 19 | passauswahl = "UB" 20 | case other: 21 | print(f"Invalid passauswahl: {other}. Valid options are: P, R, ID") 22 | exit(2) 23 | 24 | seriennummer = sys.argv[2] 25 | geburtsdatum = sys.argv[3] 26 | 27 | URL1 = "https://ausweisstatus.regioit.de/aachen" 28 | URL2 = "https://ausweisstatus.regioit.de/antragsstatusausweis_regioit/statusrequest.jsf" 29 | URL3 = "https://ausweisstatus.regioit.de/antragsstatusausweis_regioit/statusresponse.jsf" 30 | 31 | # create session for requests to preserve cookies 32 | session = requests.Session() 33 | 34 | # get session cookie and javax viewstate 35 | resp1 = session.get(URL1) 36 | soup = BeautifulSoup(resp1.text, "html.parser") 37 | viewstate = soup.find(id="javax.faces.ViewState")["value"] 38 | 39 | if not viewstate: 40 | print("Failed to determine session viewstate", file=sys.stderr) 41 | exit(10) 42 | 43 | # send post request with payload 44 | headers = { 45 | "Content-Type": "application/x-www-form-urlencoded", 46 | } 47 | data = { 48 | "antragsForm": "antragsForm", 49 | "antragsForm:passauswahl": passauswahl, # B: perso, R: reisepass, UB: eid-karte 50 | "antragsForm:seriennummer": seriennummer, 51 | "antragsForm:geburtsdatum": geburtsdatum, 52 | "antragsForm:j_id22": "Antragsstatus ermitteln", 53 | "javax.faces.ViewState": viewstate, 54 | } 55 | session.post(URL2, headers=headers, data=data) # this returns 404 for some reason but works 56 | 57 | # send get request to get current status 58 | resp3 = session.get(URL3) 59 | soup = BeautifulSoup(resp3.text, "html.parser") 60 | status_div = soup.find(id="statusAnzeige") 61 | if not status_div: 62 | print("Could not determine status, please ensure the given serial number and birthday are valid", file=sys.stderr) 63 | exit(3) 64 | status = status_div.find("strong").get_text(strip=True) 65 | print(status) 66 | 67 | session.close() 68 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | cache/ 3 | 4 | # python gitignore 5 | 6 | # Byte-compiled / optimized / DLL files 7 | __pycache__/ 8 | *.py[codz] 9 | *$py.class 10 | 11 | # C extensions 12 | *.so 13 | 14 | # Distribution / packaging 15 | .Python 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | wheels/ 28 | share/python-wheels/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | MANIFEST 33 | 34 | # PyInstaller 35 | # Usually these files are written by a python script from a template 36 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 37 | *.manifest 38 | *.spec 39 | 40 | # Installer logs 41 | pip-log.txt 42 | pip-delete-this-directory.txt 43 | 44 | # Unit test / coverage reports 45 | htmlcov/ 46 | .tox/ 47 | .nox/ 48 | .coverage 49 | .coverage.* 50 | .cache 51 | nosetests.xml 52 | coverage.xml 53 | *.cover 54 | *.py.cover 55 | .hypothesis/ 56 | .pytest_cache/ 57 | cover/ 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | 79 | # PyBuilder 80 | .pybuilder/ 81 | target/ 82 | 83 | # Jupyter Notebook 84 | .ipynb_checkpoints 85 | 86 | # IPython 87 | profile_default/ 88 | ipython_config.py 89 | 90 | # pyenv 91 | # For a library or package, you might want to ignore these files since the code is 92 | # intended to run in multiple environments; otherwise, check them in: 93 | # .python-version 94 | 95 | # pipenv 96 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 97 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 98 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 99 | # install all needed dependencies. 100 | #Pipfile.lock 101 | 102 | # UV 103 | # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. 104 | # This is especially recommended for binary packages to ensure reproducibility, and is more 105 | # commonly ignored for libraries. 106 | #uv.lock 107 | 108 | # poetry 109 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 110 | # This is especially recommended for binary packages to ensure reproducibility, and is more 111 | # commonly ignored for libraries. 112 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 113 | #poetry.lock 114 | #poetry.toml 115 | 116 | # pdm 117 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 118 | # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. 119 | # https://pdm-project.org/en/latest/usage/project/#working-with-version-control 120 | #pdm.lock 121 | #pdm.toml 122 | .pdm-python 123 | .pdm-build/ 124 | 125 | # pixi 126 | # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. 127 | #pixi.lock 128 | # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one 129 | # in the .venv directory. It is recommended not to include this directory in version control. 130 | .pixi 131 | 132 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 133 | __pypackages__/ 134 | 135 | # Celery stuff 136 | celerybeat-schedule 137 | celerybeat.pid 138 | 139 | # SageMath parsed files 140 | *.sage.py 141 | 142 | # Environments 143 | .env 144 | .envrc 145 | .venv 146 | env/ 147 | venv/ 148 | ENV/ 149 | env.bak/ 150 | venv.bak/ 151 | 152 | # Spyder project settings 153 | .spyderproject 154 | .spyproject 155 | 156 | # Rope project settings 157 | .ropeproject 158 | 159 | # mkdocs documentation 160 | /site 161 | 162 | # mypy 163 | .mypy_cache/ 164 | .dmypy.json 165 | dmypy.json 166 | 167 | # Pyre type checker 168 | .pyre/ 169 | 170 | # pytype static type analyzer 171 | .pytype/ 172 | 173 | # Cython debug symbols 174 | cython_debug/ 175 | 176 | # PyCharm 177 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 178 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 179 | # and can be added to the global gitignore or merged into this file. For a more nuclear 180 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 181 | #.idea/ 182 | 183 | # Abstra 184 | # Abstra is an AI-powered process automation framework. 185 | # Ignore directories containing user credentials, local state, and settings. 186 | # Learn more at https://abstra.io/docs 187 | .abstra/ 188 | 189 | # Visual Studio Code 190 | # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore 191 | # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore 192 | # and can be added to the global gitignore or merged into this file. However, if you prefer, 193 | # you could uncomment the following to ignore the entire vscode folder 194 | # .vscode/ 195 | 196 | # Ruff stuff: 197 | .ruff_cache/ 198 | 199 | # PyPI configuration file 200 | .pypirc 201 | 202 | # Cursor 203 | # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to 204 | # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data 205 | # refer to https://docs.cursor.com/context/ignore-files 206 | .cursorignore 207 | .cursorindexingignore 208 | 209 | # Marimo 210 | marimo/_static/ 211 | marimo/_lsp/ 212 | __marimo__/ 213 | --------------------------------------------------------------------------------