Jump to content

Code share - Automating adding movies in Radarr via Emby


noket

Recommended Posts

Hello!

Thanks to @Lukeand @hthgihwaymonkfor helping me troubleshoot some API difficulties in another thread (sessions are tough yo).

In short, this code enables the following workflow: A user can favorite a trailer and get the movie added to the server without intervention by an admin.

How it works: A user favorites a movie (requires the Trailers plugin), which triggers a webhook. The webhook calls on a localhost flask server. The flask server does some API calls to add the movie to Radarr and search for it. Emby pushes a popup notification to the user's client confirming whether it worked, or whether the movie is already being monitored (or couldn't be found).

System/Software Requirements: Python, PIP (arrapi, flask). Works under the assumption that Radarr and Emby are running on the same host.

Setting Requirements:

  1. Within Emby, Passwords must be disabled for users on localhost. There's probably a way to get around this using the admin api key, but I'm not going to fuss with it. Monk was able to get it working with the server api key, as seen here if you want to mess with it.
  2. Also, you also have to point a webhook to the flask server at URL http://127.0.0.1:5443/webhook. Make sure the "Add to favorites" option is checked. 
  3. Need to have the Trailers plugin installed on Emby

Limitations: It sends push notifications by plaintext username, instead of UserID. People with the same name are going to get random notifications. Again, I could probably fuss to fix this, but I only have 4 users and they don't have the same name.

Installation: Drop the below code into a python file (your_file_name_here.py) and save it. Modify the emby_api_key, radarr_api_key, emby_base_url, and radarr_url values as appropriate.

Running it:

  1. screen -dmS flask
  2. screen -r flask
  3. python3 your_file_name_here.py

 

from flask import Flask, request
from arrapi import RadarrAPI,exceptions

import json
import requests #https://pypi.org/project/requests/

import time
from datetime import datetime, timezone
from dateutil.parser import parse as timeparse

emby_api_key = "emby api key"
radarr_api_key = "radarr api key"
static_dict_x_emby_token = {"X-Emby-Token":emby_api_key}
emby_base_url = "http://127.0.0.1:EMBY_PORT/emby/"
radarr_url = "http://127.0.0.1:RADARR_PORT/radarr"
    
app = Flask(__name__)

@app.route('/', methods=['POST'])
def index():
    return '<h1>Flask Receiver App is Up!</h1>',200

@app.route('/webhook', methods=['POST'])
def webhook():
    if request.method == 'POST':
        form_data = request.form["data"]
        data = json.loads(form_data)
        
        #get the type of item that was favorited, and the user who did it
        favorite_type = data['Item']['Type']
        user_name = data['User']['Name']
        
        #only handle trailers
        if favorite_type == "Trailer":
            radarr = RadarrAPI(url=radarr_url,apikey=radarr_api_key)

            root_folder_path = None
            for folder in radarr.root_folder():
                root_folder_path = folder
                
            quality_profile_custom = None
            for profile in radarr.quality_profile():
                quality_profile_custom = profile

            #find the url for tmdb
            external_urls = data['Item']['ExternalUrls']
            movie_tmdb_id = ""
            for url in external_urls:
                if url['Name'] == "TheMovieDb":
                    #extract the ID from the url
                    movie_tmdb_id = url["Url"][url["Url"].index("movie/")+6:]

            #get the movie
            movie = radarr.get_movie(tmdb_id=int(movie_tmdb_id))
        
            #sometimes the radarr api call fails, loop until it succeeds
            continue_loop = True
            while continue_loop == True:
                try:    
                    #best case scenario, works on first try. notifies user in emby.
                    time.sleep(0.5)
                    movie.add(root_folder=root_folder_path,quality_profile=quality_profile_custom,minimum_availability="announced")
                    push_message(username=user_name,header="Emby Movie Download Service",message="Success! "+movie.title+" was added and, if available, will be downloaded soon!")
                    continue_loop = False
                except exceptions.Exists:
                    #movie is already monitored in radarr
                    print("The movie already exists in Radarr - "+movie.folder)
                    push_message(username=user_name,header="Emby Movie Download Service",message=movie.title+" is already being watched in the database. Once it's available, it'll be downloaded!")
                    continue_loop = False
                except exceptions.Invalid:
                    #couldn't find the tmdb
                    print("The TMDB id was invalid or couldn't be found - "+movie_tmdb_id)
                    push_message(username=user_name,header="Emby Movie Download Service",message="We couldn't find that movie with TMDB ID "+movie_tmdb_id+". Sorry!")
                    continue_loop = False
                except exceptions.ArrException as e:
                    #unknown exception, retry
                    print("Exception encountered (retrying) - "+str(e))
                finally:
                    #suffer
                    None
        return 'Webhook notification received', 202
    else:
        return 'POST Method not supported', 405

"""Pushes a message for all sessions for a single user, where the session was active within the last 5 seconds.

This method expects a header for the popup, the message for the popup, and an optional delay. If no delay is given, the message is persistent.
"""
def push_message(username:str,header:str,message:str,delay_secs:int=-1):

    predata = {"Pw":""}
    user_sessions = []

    #get all session
    sessions = requests.get(
        url=emby_base_url+"Sessions",
        headers=static_dict_x_emby_token
        ).json()
    
    #find any sessions for the user that were active within the last 5 seconds. Store the sessions in user_sessions
    for var in sessions:
        if var.get("UserName") is not None and var.get("UserId") is not None:
            if username == var.get("UserName"):
                activity_time = timeparse(timestr=var["LastActivityDate"],yearfirst=True)
                current_time = datetime.now(timezone.utc)
                time_difference = current_time-activity_time
                if time_difference.seconds < 5:
                    user_sessions.append(var)
                    
    #iterate through user_sessions and push the message to each session
    for session in user_sessions:
        user_authentication = requests.post(
            url=emby_base_url+"Users/"+session["UserId"]+"/Authenticate",
            headers=static_dict_x_emby_token,
            data=predata
            ).json()
        
        #provide the user's access token    
        message_header = {"X-Emby-Token":user_authentication["AccessToken"]}
        message_params = {"Id":session["Id"],"Text":message,"Header":header,"TimeoutMs":delay_secs*1000}
        if message_params["TimeoutMs"] == -1000:
            del message_params["TimeoutMs"]
        requests.post(
            url=emby_base_url+"Sessions/"+session["Id"]+"/Message",
            headers=message_header,
            params=message_params
        )
        
app.run(host='127.0.0.1', port=5443, debug=False)

 

Edited by noket
missed a thing again
  • Thanks 2
Link to comment
Share on other sites

2 hours ago, gihayes said:

I use Ombi, but this is cool. No need to exit the Emby to request stuff. 

Doesn't Ombi provide the same feature? (liking to request an item)

Link to comment
Share on other sites

Maybe 🤔

Didn't know about Ombi. Seems like cool software, probably a bit overkill for my use case - I just wanted to add movies when someone hits favorite.

  • Like 1
Link to comment
Share on other sites

50 minutes ago, noket said:

Maybe 🤔

Didn't know about Ombi. Seems like cool software, probably a bit overkill for my use case - I just wanted to add movies when someone hits favorite.

Yes, Ombi is a kind of swiss-army-knife in this area.

Thanks a lot for sharing your solution!

  • Like 1
Link to comment
Share on other sites

gihayes
1 hour ago, softworkz said:

Doesn't Ombi provide the same feature? (liking to request an item)

You can't request from within Emby. You have to go to Ombi to request. You can link to Emby from Ombi tho. I put the link to Ombi on my Emby login page. I like Ombi cause it can handle any Movie, TV, or Music request and also notifies the user when the request has been added to Emby. 

Link to comment
Share on other sites

I could swear there was some integration with Emby where you could see available items in Emby and request them by hitting "Like" button.
But maybe it was an add-on to Ombi.

Link to comment
Share on other sites

TeamB

the liking the trailer to get the movie is a very smart way to do this, good work.

you should look at turning this into a plugin. you may not be able to add it to the official emby plugin catalogue due to reasons, but if you create a plugin people will be able to drop it in the plugin dir to use it.

good work.

  • Agree 1
  • Thanks 1
Link to comment
Share on other sites

bakes82

Overseer/Ombi would be better than direct Sonarr/Radarr integration as you could limit the number of requests and a user could track their own requests if you associate the request to a user in overseer/ombi, plus what if you run multi ARR for 4k/1080p etc ;)  Plus its only one api instead of 2.

  • Agree 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...