├── 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 |
--------------------------------------------------------------------------------