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
