Python Flask Waitress WebHook that sends WakeOnLan command if NAS is in s3 Sleep

Environment:
There is a plex server on windows. This server is always on and is multi-purpose.

There is an old unRAID server that hosts movies and tv shows libraries. This unRAID server only functions as storage (no other processes), so it “sleeps” after no activity. It can be “awakened” via a wakeonlan (WOL) broadcast.

Plex Config:
Plex libraries include music, movies, tv shows, and live tv (hdhomerunprime with cablecard). Any libraries that reference the unRAID server have “Our” in the title (e.g. Our Movies). The libraries that do not reference the unRAID server do not contain “Our”.

Problem:
How to wake the unRAID server when it is asleep via Plex only if unRAID content is requested? (only wake the unRAID server content is “played” from plex from a library in plex that contains “Our”).

Solution:
Write some quick and dirty python code to receive a webhook from Plex and then act accordingly. Tautilli was first attempted and while a custom trigger with proper conditions could be made, I did not achieve success in all scenarios.

The Code calls an external small executable to send the WOL packets. Already had the exe, so didn’t add the functionality in python.

When the unRAID server is up, there webUI is available and is being used to determine if unRAID is asleep or “awake”. SMB or SSH could be used as well, but webUI check seemed easier.

Webhook added to plex for localhost:xxxx/webhook as this python code is running on the plexserver.

Currently running via Windows Scheduler as creating a windows service resulted in too many issues around logging which I may revisit later. Use code or modify at your own peril - not claiming to be a developer, but this has worked well for me and took less than 15 minutes (quick and dirty) to solve my problem. However, always happy to make things better and receive constructive feedback.

Code:
Replace the port, MACaddress, unRAID URL accordingly

import os
import json
import logging
import requests
import subprocess
from logging.handlers import TimedRotatingFileHandler
from waitress import serve
from flask import Flask, abort, request

app = Flask(__name__)

# Configure logging to write to a file
log_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'waitressplexhookapp.log')
handler = TimedRotatingFileHandler(log_file, when='midnight', backupCount=7)
handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(message)s'))
logging.getLogger().addHandler(handler)
logging.getLogger().setLevel(logging.INFO)

@app.route('/webhook', methods=['POST'])
def webhook():
    """
    Flask route that listens for POST requests on the /webhook endpoint.
    If the JSON payload contains a key called librarySectionTitle and it contains the word "Our", the app checks if the unRAID website is up and running.
    If the unRAID website is up, the app returns "success" with an HTTP status code of 200.
    If the unRAID website is down, the app calls an external executable to wake up the unRAID server and returns a message indicating that unRAID is being woken up.
    """
    if request.method == 'POST':
        try:
            data = json.loads(request.form['payload'])
            library_section_title = find_library_section_title(data)
            if library_section_title == False:
                logging.warning('Not a library on unRAID')
                return 'Not a library on unRAID', 200
            else:
                url = "http://xxUNRAIDURLxx"
                is_up = is_website_up(url)
                if is_up == True:
                    logging.info('UNRAID is Up')
                    return 'success', 200
                else:
                    exe_path = "E:\\WakeOnLan\\wolcmd xxMACAddressofUNRAIDxx 255.255.255.255 255.255.255.255"
                    result = call_exe(exe_path)
                    if result:
                        logging.warning('unRAID is Down so WakeOnLan called')
                        return 'unRAID is Down so WakeOnLan called', 200
                    else:
                        logging.error('Error calling WakeOnLan')
                        return 'Error calling WakeOnLan', 500
        except Exception as e:
            logging.error(f'Error: {e}')
            abort(500)
    else:
        logging.warning('Invalid request method')
        abort(400)

def is_website_up(url):
    """
    Checks if a website is up and running.
    """
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return True
        else:
            return False
    except requests.ConnectionError:
        return False

def call_exe(exe_path):
    """
    Calls an external executable.
    """
    try:
        subprocess.run(exe_path)
        return True
    except FileNotFoundError:
        logging.error(f"Error: {exe_path} not found")
        return False

def find_library_section_title(data):
    """
    Finds the librarySectionTitle key in a JSON payload and checks if it contains the word "Our".
    """
    try:
        if 'librarySectionTitle' in data['Metadata']:
            library_section_title = data['Metadata']['librarySectionTitle']
            if 'Our' in library_section_title:
                return library_section_title
            else:
                return False
        else:
            return False
    except (KeyError, json.JSONDecodeError):
        logging.error("Error: Invalid JSON payload")
        return False

if __name__ == '__main__':
    #app.run(host="localhost", port=xxxx, debug=True) #non-prod testing
    serve(app, host='localhost', port=xxxx, threads=1) #"prod" with waitress

Note sure how to paste a codeblock in this forum, so the comments in the code and indents are problematic in displaying - hopefully that is understood

Select desired text, then format using </>.

Screenshot (1694)

1 Like

Thank you!