diff --git a/.github/workflows/image-build.yml b/.github/workflows/image-build.yml
new file mode 100644
index 0000000..d735988
--- /dev/null
+++ b/.github/workflows/image-build.yml
@@ -0,0 +1,25 @@
+name: Build Docker Image
+on:
+ push:
+ branches:
+ - main
+jobs:
+ build:
+ name: push docker image to docker hub
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: login to docker hub
+ id: docker-hub
+ env:
+ username: ${{secrets.DOCKER_USERNAME}}
+ password: ${{secrets.DOCKER_TOKEN}}
+ run: |
+ docker login -u $username -p $password
+ - name: build the docker image
+ id: build-docker-image
+ run: |
+ docker build -t michivonah/ep-alerts:latest .
+ - name: push the docker image
+ id: push-docker-image
+ run: docker push michivonah/ep-alerts:latest
diff --git a/.gitignore b/.gitignore
index dc12cb7..755fb4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
.env
-__pycache__
\ No newline at end of file
+__pycache__
+venv
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index 343567e..c53c2a6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM python:3.11
+FROM python:3.12
# Create directory
RUN mkdir app
@@ -11,6 +11,7 @@ COPY requirements.txt .
# Set enviromental variables
ENV DISCORD_WEBHOOK "https://discord.com/api/webhooks/XXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYY"
ENV SUBS "383533, 323530, 353030"
+ENV NOTIFICATION_TYPE "discord"
# Install needed packages
RUN pip3 install --upgrade pip
diff --git a/README.md b/README.md
index afa6679..4178c2c 100644
--- a/README.md
+++ b/README.md
@@ -1,10 +1,11 @@
-# Europapark Waitingtime Alerts via Discord Webhook
-A tool which alerts you when the waiting times of subscribed europapark attractions sinks or increase. Powered by the [wartezeiten.app](https://www.wartezeiten.app/page/api.html) API.
+# Themepark Wait Time Alerts
+
+A tool which alerts you when the waiting times of subscribed attractions in your favourite themepark sinks or increase. The notifications are sent to a Discord webhook or to your ntfy-server. Powered by the [wartezeiten.app](https://www.wartezeiten.app/page/api.html) API.
-GitHub: https://github.com/michivonah/europapark-alerts
+GitHub: https://github.com/michivonah/themepark-alerts
Docker: https://hub.docker.com/r/michivonah/ep-alerts
-Host it on your server:
+Host it on your own server:
1. Install docker on your system
```apt-get install docker.io docker-compose -y```
@@ -15,9 +16,15 @@ Host it on your server:
## Enviormental variables
These environment variables are supported
-| Variable | Description | Example |
-| --- | --- | --- |
-| DISCORD_WEBHOOK | The URL of your discord webhook | ``https://discord.com/api/webhooks/XXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYY`` |
-| SUBS | Your subscribed attractions with ID from wartezeiten.app API | ``383533,323530,353030`` |
+| Variable | Description | Example | Required |
+| --- | --- | --- | --- |
+| NOTIFICATION_TYPE | Define which type of notification you want use. Supported are: discord, ntfy | ``discord`` | yes |
+| CHECK_INTERVAL | Defines how often the API is requested and the waittimes are checked for updates (in seconds) | ``30`` | no |
+| DISCORD_WEBHOOK | The URL of your discord webhook | ``https://discord.com/api/webhooks/XXXXXXXXXXXXXX/YYYYYYYYYYYYYYYYYYYYYYYYY`` | no* |
+| NTFY_URL | The URL to your ntfy topic | ``https://ntfy.example.com/mytopic`` | no* |
+| NTFY_ACCESS_TOKEN | Optional access token for authenticating against your ntfy server if authentication is enabled. | ``tk_2cdbcfea1702cc3bd2c874beab1`` | no |
+| SUBS | Your subscribed attractions with ID from wartezeiten.app API | ``383533,323530,353030`` | yes |
+| THEMEPARK | Select your desired themepark from the wartezeiten.app API. Defaults to ``europapark`` | ``europapark`` | no |
-> Required enviromental variables: DISCORD_WEBHOOK, SUBS
\ No newline at end of file
+
+> *Depending on the selected service for notifications (``NOTIFICATION_TYPE``) ether ``DISCORD_WEBHOOK`` or ``NTFY_URL`` is required.
\ No newline at end of file
diff --git a/main.py b/main.py
index 9654dc2..f31846e 100644
--- a/main.py
+++ b/main.py
@@ -1,11 +1,10 @@
-# Europapark Waiting Time alerts to Discord
-# Michi von Ah - October 2023
+# Themepark Wait Time Alerts
+# Michi von Ah - October 2023 (Last Updated on October 2024)
# Thanks to https://www.wartezeiten.app/ for their API
import requests
import os
from dotenv import load_dotenv
-from discord_webhook import DiscordWebhook
import time
load_dotenv()
@@ -13,49 +12,90 @@ load_dotenv()
# Global defintions
subscribedAttractions = os.getenv('SUBS').split(",")
currentTimes = {}
-refreshTime = 30
+refreshTime = int(os.getenv('CHECK_INTERVAL')) if os.getenv('CHECK_INTERVAL') else 30
+notificationType = os.getenv('NOTIFICATION_TYPE') if os.getenv('NOTIFICATION_TYPE') else "discord"
+themepark = os.getenv('THEMEPARK') if os.getenv('THEMEPARK') else "europapark"
-# Send messagess via Discord Webhook
-def sendMessage(message):
- webhookUrl = os.getenv('DISCORD_WEBHOOK')
- webhook = DiscordWebhook(url=webhookUrl, content=message)
- response = webhook.execute()
- return response
+# Send messagess via specified notificationType
+def sendMessage(message, notificationType):
+ match notificationType.lower():
+ case "ntfy":
+ endpoint = os.getenv('NTFY_URL')
+ accessToken = os.getenv('NTFY_ACCESS_TOKEN')
+ if accessToken:
+ header = {
+ "Authorization":f"Bearer {accessToken}"
+ }
+ try:
+ response = requests.post(url=endpoint, headers=header, data=message)
+ except Exception as error:
+ raise Exception(f"Got an error while sending the notification: {error}")
+ else:
+ try:
+ response = requests.post(url=endpoint, data=message)
+ except Exception as error:
+ raise Exception(f"Got an error while sending the notification: {error}")
+ return response
+ case "discord" | _:
+ endpoint = os.getenv('DISCORD_WEBHOOK')
+ data = {
+ "content": message,
+ }
+ try:
+ response = requests.post(url=endpoint, json=data)
+ return response
+ except Exception as error:
+ raise Exception(f"Got an error while sending the notification: {error}")
# Check for the current waiting times
-def checkTimes(subscribedAttractions):
- endpoint = "https://api.wartezeiten.app/v1/waitingtimes"
+def checkTimes(subscribedAttractions, themepark):
+ try:
+ endpoint = "https://api.wartezeiten.app/v1/waitingtimes"
- header = {
- "language":"de",
- "park":"europapark"
- }
+ header = {
+ "language":"de",
+ "park":themepark
+ }
- req = requests.get(url = endpoint, headers = header)
- result = req.json()
- attractions = result
- for attraction in attractions:
- if attraction["code"] in subscribedAttractions:
- if attraction["status"] == "opened":
- refreshTime = 30
- if not attraction["code"] in currentTimes: currentTimes[attraction["code"]] = attraction["waitingtime"];
- if currentTimes[attraction["code"]] > attraction["waitingtime"]:
- sendMessage(f"Waiting time of {attraction['name']} sank to {attraction['waitingtime']} Minutes!")
- elif currentTimes[attraction["code"]] < attraction["waitingtime"]:
- sendMessage(f"Waiting time for {attraction['name']} increased to {attraction['waitingtime']} Minutes!")
- currentTimes[attraction["code"]] = attraction["waitingtime"]
+ req = requests.get(url=endpoint, headers=header)
+ except:
+ raise Exception(f"API Request to endpoint {endpoint} failed.")
+ try:
+ result = req.json()
+ except:
+ raise Exception("Format of API response is invalid. (JSON expected)")
+ try:
+ attractions = result
+ for attraction in attractions:
+ if isinstance(attraction, dict) and "code" in attraction:
+ if attraction["code"] in subscribedAttractions:
+ if attraction["status"] == "opened":
+ refreshTime = 30
+ if not attraction["code"] in currentTimes: currentTimes[attraction["code"]] = attraction["waitingtime"];
+ if currentTimes[attraction["code"]] > attraction["waitingtime"]:
+ sendMessage(f"Waiting time of {attraction['name']} sank to {attraction['waitingtime']} Minutes!", notificationType)
+ elif currentTimes[attraction["code"]] < attraction["waitingtime"]:
+ sendMessage(f"Waiting time for {attraction['name']} increased to {attraction['waitingtime']} Minutes!", notificationType)
+ currentTimes[attraction["code"]] = attraction["waitingtime"]
+ else:
+ refreshTime = 180
else:
- refreshTime = 180
+ print(f"Info: Attraction was skipped because it has an invalid data structure. Affected attraction: {attraction}")
+ except Exception as error:
+ raise Exception(f"Got an error while checking for differences since the last API call. Error: {error}")
# Main Loop
# Checks every 30 seconds for changes in the waiting times of the subscribed attractions
# If some attractions are closed the check will only be executed every 180 seconds
if __name__ == '__main__':
- print("EP Waiting Time Alerting Tool")
+ print("Themepark Wait Time Alerts")
print("By Michi von Ah")
print("Big thanks to the wartezeiten.app API!")
while True:
- checkTimes(subscribedAttractions)
- print(f"Checked for updates at {time.strftime('%H:%M:%S', time.localtime())}")
+ try:
+ checkTimes(subscribedAttractions, themepark)
+ print(f"Checked for updates at {time.strftime('%H:%M:%S', time.localtime())}")
+ except Exception as error:
+ raise Exception(error)
time.sleep(refreshTime)
diff --git a/media/banner.jpg b/media/banner.jpg
new file mode 100644
index 0000000..6b08afd
Binary files /dev/null and b/media/banner.jpg differ
diff --git a/requirements.txt b/requirements.txt
index c310a0d..a8ecb02 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,4 +1,4 @@
# requirements.txt
-python-dotenv==1.0.0
-#discord.py
-discord-webhook==1.3.0
\ No newline at end of file
+python-dotenv==1.1.1
+pipenv==2025.0.4
+requests==2.32.5
\ No newline at end of file