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 +![](media/banner.jpg) +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