
    gs                    (   d dl Z d dlZd dlmZmZ d dlZd dlZd dlZd dlZd dl	Z
d dlZd dlZd dlZd dlmZ d dlZd dlmZmZmZ d dlmZ d dlmZ d dlmZ d dlZd dlZd dlZd dlZej@                  dk\  rd dl!Z!d dl"Z"d d	l"m#Z# d d
l"m$Z$ d dl"m%Z% d dl"m&Z& d dl"m'Z' d dl"m(Z( d dl"m)Z) d dl"m*Z* d dl"m+Z+ d dl"m,Z, d dl"m-Z- d dl"m.Z. d dl"m/Z/ d dl"m0Z0 d dl"m1Z1 d dl"m2Z2 d dl"m3Z3 d dl"m4Z4 d dl"m5Z5 d dl"m6Z6 d dl"m7Z7 d dl"m8Z8 d dl"m9Z9 d d l"m:Z: d d!l"m;Z; d d"l"m<Z< d d#l=m>Z> d d$l?m@Z@mAZAmBZBmCZCmDZDmEZE d d%lFmGZGmHZHmIZImJZJ d d&lKmLZLmMZMmNZNmOZOmPZP e%j                  d'k(  rd d(l"mRZR ne%j                  d)k(  rd d*l"mSZS daTd+ ZUd, ZV G d- d.eW      ZX G d/ d0eW      ZYy)1    N)openBytesIO)	urlencode)
serve_fileserve_fileobjserve_download)NotFound)	make_hash)TemplateLookup      )activity_pinger)activity_processor)commonconfigdatabase)datafactory)exporter)graphs)helpers)http_handler)	libraries)
log_reader)logger)newsletter_handler)newsletters)
mobile_app)notification_handler)	notifiers)plextv)plexivity_import)plexwatch_import)
pmsconnect)users)versioncheck)
web_socket)webstart)API2)checkedaddtoapiget_ipcreate_https_certificatesbuild_datatables_jsonsanitize_out)get_session_infoget_session_user_idallow_session_userallow_session_library)AuthControllerrequireAuth	member_of
check_authget_jwt_tokenWindows)windowsDarwin)macosc                    t         t        j                  j                  t	        t
        j                        d      }t        j                  j                  t	        |      t
        j                  j                        }t        |gddgt              a t
        j                  }t        j                         }dt
        j                  xs t        j                   z   }t#               }	 t         j%                  |       } |j&                  d||||d|S # t(        $ rN}	t+        j,                  d|	z         t.        j0                  j3                         j'                         cY d }	~	S d }	~	ww xY w)	Nzdata/interfaces/unicodeh)directoriesdefault_filterserror_handler?)	http_rootserver_namecache_param_sessionz'WebUI :: Mako template render error: %s )TEMPLATE_LOOKUPospathjoinstrplexpyPROG_DIRCONFIG	INTERFACEr   mako_error_handler	HTTP_ROOTr   pms_nameCURRENT_VERSIONr   RELEASEr2   get_templaterender	Exceptionr   	exceptionmako
exceptionshtml_error_template)
template_namekwargsinterface_dirtemplate_dirrF   rG   rH   rI   templatees
              /opt/Tautulli/plexpy/webserve.pyserve_templaterg   V   s   S%9;MNww||C$68O8OP(l^V_adUe*<>   I""$K//A6>>BK!H>"//>x <Yd(0<4:< 	< >BQFG224;;==>s   (+D 	E+AE& E+&E+c                    t         j                  j                  |      }t        |j                        }t        j                         d   }i }t               }|t        |      }|j                  }|j                  }	|	j                  }
|j                  }|
j                  d      r|j                  |
      }|Vt         j                  j!                  |
      }|j"                  j%                  d      x}||
<   dd||
ft&        j(                  |
<   |
|f|vr2|j+                  |
|f        dj,                  | }||dz
  xx   |z  cc<   |j.                  }| )zDecorate tracebacks when Mako errors happen.
    Evil hack: walk the traceback frames, find compiled Mako templates,
    stuff their (transformed) source into linecache.cache.
    Nzmemory:Tz     # {} line {} in {}:
    # {}   )r]   r^   RichTracebackiter	tracebacksysexc_infosetnexttb_framef_codeco_filename	tb_lineno
startswithgetrd   _get_module_infomodule_source
splitlines	linecachecacheaddformattb_next)contexterrorrich_tb	rich_itertbsource	annotatedcur_richfcofilenamelinenolinesinfoextras                  rf   rT   rT   m   s<   
 oo++E2GW&&'I		BFI
.	?KKXX>>y)JJx(E}}}55h?+/+=+=+H+H+NNx(-14,I	)&!2x01B;BBHMfqj!U*!ZZ! .& 
    c                   T    e Zd Zej                  d        Zej                  d        Zy)BaseRedirectc                 H    t        j                  t        j                        N)cherrypyHTTPRedirectrP   rU   selfs    rf   indexzBaseRedirect.index   s    ##F$4$455r   c                     |rddj                  |      z   nd}|rdt        |      z   nd}t        j                  t        j
                  dz   |z   |z         )N/ rE   status)rN   r   r   r   rP   rU   )r   argsra   rM   querys        rf   r   zBaseRedirect.status   sQ    '+sSXXd^#+1i''r##F$4$4x$?$$F$NOOr   N)__name__
__module____qualname__r   exposer   r   rJ   r   rf   r   r      s2    __6 6 __P Pr   r   c                   `   e Zd Z e       Zd Zej                   e       d               Z	ej                   e e
d            d               Zej                  ej                  j                          e e
d            dd                     Zej                  ej                  j                          e e
d             ed      dd                            Zej                   e       d	               Zej                  ej                  j                          e        e       d
                             Zej                   e       d               Zej                   e       dd              Zej                  ej                  j                          e e
d             e       dd                            Zej                   e e
d            dd              Zej                   e       dd              Zej                   e       d               Zej                   e       dd              Zej                  ej                  j                          e e
d             e       d                             Zej                  ej                  j                          e e
d             e       d                             Zej                  ej                  j                          e e
d             e       d                             Zej                   e       d               Zej                  ej                  j                          e        e        ed      dd                                   Zej                  ej                  j                          e e
d             e        ed      d                                    Z ej                  ej                  j                          e e
d            d                      Z!ej                   e       dd              Z"ej                   e e
d            dd              Z#ej                   e e
d             e       dd                     Z$ej                   e       dd              Z%ej                   e       dd              Z&ej                   e       dd               Z'ej                   e       dd!              Z(ej                  ej                  j                          e e
d             e       dd"                            Z)ej                  ej                  j                          e        ed#      dd$                            Z*ej                  ej                  j                          e        ed%      dd&                            Z+ej                  ej                  j                          e e
d            dd'                     Z,ej                  ej                  j                          e e
d             e       dd(                            Z-ej                  ej                  j                          e e
d             e       dd)                            Z.ej                  ej                  j                          e e
d             e       dd*                            Z/ej                  ej                  j                          e e
d             e       dd+                            Z0ej                  ej                  j                          e e
d             e       dd,                            Z1ej                  ej                  j                          e e
d             e       dd-                            Z2ej                  ej                  j                          e e
d             e       d.                             Z3ej                  ej                  j                          e e
d            d/                      Z4ej                   e       d0               Z5ej                  ej                  j                          e        e        ed1      dd2                                   Z6ej                  ej                  j                          e e
d            d3                      Z7ej                   e       dd4              Z8ej                   e e
d            dd5              Z9ej                   e e
d             e       dd6                     Z:ej                   e       dd7              Z;ej                   e       dd8              Z<ej                   e       dd9              Z=ej                  ej                  j                          e        e        e       dd:                                   Z>ej                  ej                  j                          e        e        e       dd;                                   Z?ej                  ej                  j                          e e
d             e       dd<                            Z@ej                  ej                  j                          e e
d             e       dd=                            ZAej                  ej                  j                          e e
d             e       dd>                            ZBej                  ej                  j                          e e
d             e       dd?                            ZCej                  ej                  j                          e e
d             e       dd@                            ZDej                  ej                  j                          e e
d             e       ddA                            ZEej                  ej                  j                          e e
d             e       ddB                            ZFej                   e       dC               ZGej                  ej                  j                          e        e        e       ddD                                   ZHej                   e       ddE              ZIej                  ej                  j                          e        edF      ddG                            ZJej                   e       ddH              ZKej                  ej                  j                          e e
d             edI      ddJ                            ZLej                   e       dK               ZMej                  ej                  j                          e        e        e       dL                                    ZNej                  ej                  j                          e        e       d dM                            ZOej                  ej                  j                          e        e       d dN                            ZPej                  ej                  j                          e        e       d dO                            ZQej                  ej                  j                          e        e       ddP                            ZRej                  ej                  j                          e        e       ddQ                            ZSej                  ej                  j                          e        e       ddR                            ZTej                  ej                  j                          e        e       ddS                            ZUej                  ej                  j                          e        e       ddT                            ZVej                  ej                  j                          e        e       ddU                            ZWej                  ej                  j                          e        e       ddV                            ZXej                  ej                  j                          e        e       ddW                            ZYej                  ej                  j                          e        e       ddX                            ZZej                   e       dY               Z[ej                   e       dZ               Z\ej                  ej                  j                          e        e       dd[                            Z]ej                  ej                  j                          e e
d             ed\      dd]                            Z^ej                   e e
d            d^               Z_ej                   e e
d            dd_              Z`ej                  ej                  j                          e e
d             e       dd`                            Zaej                  ej                  j                          e e
d             e        e       da                                    Zbej                  ej                  j                          e e
d             e        e       db                                    Zcej                  ej                  j                          e e
d             e       dc                             Zdej                  ej                  j                          e e
d             e       dd                             Zeej                  ej                  j                          e e
d             e       de                             Zfej                  ej                  j                          e e
d            ddf                     Zgej                   e e
d            dg               Zhej                   e       dh               Ziej                   e e
d            ddi              Zjej                   e e
d            dj               Zkej                  ej                  j                          e e
d            dk                      Zlej                  ej                  j                          e e
d            dl                      Zmej                  ej                  j                          e e
d            dm                      Znej                  ej                  j                          e e
d            dn                      Zoej                  ej                  j                          e e
d            do                      Zpej                   e e
d            dp               Zqej                   e e
d            dq               Zrej                   e e
d            ddr              Zsej                  ej                  j                          e e
d            ds                      Ztej                  ej                  j                          e e
d            dt                      Zuej                  ej                  j                          e e
d             e       ddu                            Zvej                   e e
d            dv               Zwej                  ej                  j                          e e
d             e       ddw                            Zxej                  ej                  j                          e e
d             e       ddx                            Zyej                   e e
d            ddy              Zzej                  ej                  j                          e e
d             e       ddz                            Z{ej                  ej                  j                          e e
d             e       dd{                            Z|ej                   e e
d            dd|              Z}ej                  ej                  j                          e e
d             e       d}                             Z~ej                  ej                  j                          e e
d            dd~                     Zej                  ej                  j                          e e
d            d                      Zej                  ej                  j                          e e
d            dd                     Zej                   e e
d            dd              Zej                  ej                  j                          e e
d            d                      Zej                   e e
d            d               Zej                  ej                  j                          e e
d            dd                     Zej                   e e
d            d               Zej                   e e
d            d               Zej                  ej                  j                          e e
d            dd                     Zej                   e e
d            dd              Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       dd                            Z ej                  di ddiej                  ej                  j                          e e
d             e       	 	 dd                                   Zej                  ej                  j                          e e
d             e       d	d                            Zej                   e e
d            dd              Zej                   e e
d            d               Zej                  ej                  j                          e e
d            d
d                     Zej                  ej                  j                          e e
d             e       	 	 dd                            Zej                  ej                  j                          e e
d             e       d                             Zej                   e e
d             e       dd                     Zej                  ej                  j                          e e
d            dd                     Zej                  ej                  j                          e e
d             e       d                             Zd Zej                   e e
d            d               Zej                   e e
d            d               Zej                   e e
d            d               Zej                   e e
d            dd              Zej                   e e
d            d               Zej                   e e
d            d               Zej                   e e
d            dd              Zej                   e       dd              Zej                   e       dd              Zej                   e       dd              Zej                   e       dd              Zej                   e       dd              Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             ed      dd                            Zej                  ej                  j                          e e
d             ed      dd                            Zej                  d        Z ed      	 	 	 dd       Zej                  d        Zej                   e e
d             e       d                      Zej                   e e
d             e       d                      Zej                   e e
d             e       dd                     Zej                   e e
d             e       dd                     Zej                  ej                  j                          e e
d             e       d                             Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       dd                            Zej                   e       dd              Zej                  ej                  j                          e e
d             ed      dd                            Zej                   e       dd              Zej                   e e
d            d	d              Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d            d                      Zej                  ej                  j                          e e
d             ed      dd                            Zej                  ej                  j                          e e
d             ed      dd                            Zej                  ej                  j                          e e
d            dÄ                      Zej                  ej                  j                          e e
d            dĄ                      Zej                  ej                  j                          e e
d            dń                      Zej                  ej                  j                          e e
d            ddƄ                     Zej                  ej                  j                          e e
d            dǄ                      Zej                  ej                  j                          e e
d             e       dȄ                             Zej                  ej                  j                          e e
d             e       dɄ                             Zej                  ej                  j                          e e
d             e       dʄ                             Zej                  ej                  j                          e        e       dd˄                            Zej                  ej                  j                          e e
d             ed̫      d̈́                             Zej                  ej                  j                          e e
d             edΫ      dτ                             Zej                  ej                  j                          e e
d             e        e       ddЄ                                   Zej                  ej                  j                          e e
d            dф                      Zej                  ej                  j                          e e
d             e       	 	 	 dd҄                            Zej                   e e
d             edӫ      dԄ                      Zej                  dՄ        Zej                  ej                  j                          e e
d             e       dք                             Zej                  ej                  j                          e e
d             e       dׄ                             Zej                  ej                  j                          e        e       dd؄                            Zej                  ej                  j                          e        e       ddل                            Zej                  ej                  j                          e e
d            dڄ                      Zej                  ej                  j                          e e
d             e       dۄ                             Zej                   e e
d            d܄               Zej                  ej                  j                          e e
d             e       dd݄                            Zej                  ej                  j                          e e
d             e       ddބ                            Zej                   e e
d            dd߄              Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d            dd                     Zej                  d        Zej                   e       d               Zej                   e e
d            d               Zej                   e e
d            	 	 dd              Zej                   e e
d            d               Zej                  ej                  j                          e       d                      Zej                  ej                  j                          e       d                      Zej                  ej                  j                          e e
d             ed      dd                            Zej                   e e
d            	 	 	 dd              Zej                  ej                  j                          e e
d             e       dd                            Zej                  ej                  j                          e e
d             e       	 	 	 	 dd                            Zej                   e e
d            dd              Zej                   e e
d             e       dd                     Zej                  ej                  j                          e e
d             e       dd                            Zej                   e e
d            d               Zy(  WebInterfacec                 |    t         j                  j                  t        t        j
                        d      | _        y )Nzdata/)rL   rM   rN   rO   rP   rQ   rb   r   s    rf   __init__zWebInterface.__init__   s"    WW\\#foo*>Hr   c                     t         j                  j                  r&t        j                  t         j
                  dz         t        j                  t         j
                  dz         )Nhomewelcome)rP   rR   FIRST_RUN_COMPLETEr   r   rU   r   ra   s     rf   r   zWebInterface.index   sI     ==++''(8(86(ABB''(8(89(DEEr   adminc                    t         j                  j                  t         j                  j                  t         j                  j                  t         j                  j
                  t         j                  j                  t        j                         t         j                  j                  d}t         j                  j                  r:t        j                          t        j                  t         j                  dz         t        dd|      S )N)pms_identifierpms_ippms_portpms_sslpms_is_cloudrV   logging_ignore_intervalr   zwelcome.htmlWelcomer`   titler   )rP   rR   PMS_IDENTIFIERPMS_IPPMS_PORTPMS_SSLPMS_IS_CLOUDr   rV   LOGGING_IGNORE_INTERVALr   initialize_schedulerr   r   rU   rg   r   ra   r   s      rf   r   zWebInterface.welcome   s     %mm::mm**..}},,"MM66((*'-}}'L'L
 ==++'')''(8(86(ABB!iX^__r   Nc                     ||t         j                  _        ||t         j                  _        t         j                  j	                          y r   )rP   rR   	PMS_TOKENPMS_CLIENT_IDwrite)r   token	client_idra   s       rf   save_pms_tokenzWebInterface.save_pms_token   s8     &+FMM# *3FMM'r   get_server_listc                 r    |dk(   }|dk(   }t        j                         }|j                  ||      }|r|S y)a   Get all your servers that are published to Plex.tv.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"clientIdentifier": "ds48g4r354a8v9byrrtr697g3g79w",
                      "httpsRequired": "0",
                      "ip": "xxx.xxx.xxx.xxx",
                      "label": "Winterfell-Server",
                      "local": "1",
                      "port": "32400",
                      "value": "xxx.xxx.xxx.xxx"
                      },
                     {...},
                     {...}
                     ]
            ```
        false)include_cloudall_serversN)r#   PlexTVdiscover)r   r   r   ra   plex_tvservers_lists         rf   r   zWebInterface.discover   sT    : +g56&'12--/''m4? ( A  r   c                 D   t         j                  j                  t         j                  j                  t	        j
                         t         j                  j                  t         j                  j                  t         j                  j                  d}t        dd|      S )N)home_sectionshome_refresh_intervalrV   r   update_show_changelogfirst_run_completez
index.htmlHomer   )
rP   rR   HOME_SECTIONSHOME_REFRESH_INTERVALr   rV   r   UPDATE_SHOW_CHANGELOGr   rg   r   s      rf   r   zWebInterface.home   si     $]]88%+]]%H%H((*"MM66%+]]%H%H"(--"B"B
 LvVVr   c                     t         j                  j                  rt         j                  j                  }nd}t         j                  j                  rt         j                  j                  }nd}||d}|S )aj   Get the date and time formats used by Tautulli.

             ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    {"date_format": "YYYY-MM-DD",
                     "time_format": "HH:mm",
                     }
            ```
        z
YYYY-MM-DDzHH:mm)date_formattime_format)rP   rR   DATE_FORMATTIME_FORMAT)r   ra   r   r   formatss        rf   get_date_formatszWebInterface.get_date_formats   sZ    * ==$$ --33K&K==$$ --33K!K"-"-/ r   c                     t        j                  t        j                  j                        }|j                         }|rt        d|      S t        j                  d       t        dd       S )Nr   zcurrent_activity.htmlr`   dataz1Unable to retrieve data for get_current_activity.)	r&   
PmsConnectrP   rR   r   get_current_activityrg   r   warnr   ra   pms_connectresults       rf   r   z!WebInterface.get_current_activity#  sX     !++&--2I2IJ113!0GfUUKKKL!0GdSSr   c                     t        j                  t        j                  j                        }|j                         }|r%t        fd|d   D        d       }t        d|      S t        dd       S )Nr   c              3   4   K   | ]  }|d    k(  s|  ywsession_keyNrJ   .0sr   s     rf   	<genexpr>z=WebInterface.get_current_activity_instance.<locals>.<genexpr>8  s     ]!Q}=MQ\=\A]   sessionszcurrent_activity_instance.html)r`   session)r&   r   rP   rR   r   r   rq   rg   )r   r   ra   r   r   r   s    `    rf   get_current_activity_instancez*WebInterface.get_current_activity_instance0  sf     !++&--2I2IJ113]vj'9]_cdG!0PZabb!0PZ^__r   c                     t        j                         }|j                  |||      }t        |t              rddj                  |      dS |du rdddS dddS )	a   Stop a streaming session.

            ```
            Required parameters:
                session_key (int):          The session key of the session to terminate, OR
                session_id (str):           The session id of the session to terminate

            Optional parameters:
                message (str):              A custom message to send to the client

            Returns:
                None
            ```
        )r   
session_idmessager   z Failed to terminate session: {}.r   r   TsuccesszSession terminated.zFailed to terminate session.)r&   r   terminate_session
isinstancerO   r~   )r   r   r   r   ra   r   r   s          rf   r   zWebInterface.terminate_session=  sl    & !++-..;S]gn.ofc"%2T2[2[\b2cddt^'4IJJ%2PQQr   c                 F   t        j                  |      rd}n6t        j                  j                  xs t        j                  j
                  }d|v r*|j                  t        j                  j                        }||z   |rdt        |      z   ndz   }t        dd|      S )	Nzhttps://plex.tvz{machine_id})
machine_idrE   r   zxml_shortcut.htmlzPlex XML)r`   r   url)
r   	bool_truerP   rR   PMS_URL_OVERRIDEPMS_URLr~   r   r   rg   )r   endpointr#   ra   base_urlr   s         rf   open_plex_xmlzWebInterface.open_plex_xmlZ  s     V$(H}}55N9N9NHX%&--2N2NOH!S9V+<%<BO,?zWZ[[r   c                 n    t        j                         }|j                  |||      }t        dd|      S )N)
time_range
stats_typestats_countzhome_stats.htmlStatsr`   r   r   )r   DataFactoryget_home_statsrg   )r   r  r  r  ra   data_factory
stats_datas          rf   
home_statszWebInterface.home_statsh  sC     #..0!00J<F=H 1 J
 ,=WS]^^r   c                     t        j                         }t        j                  j                  }|j                  |      }t        dd|      S )N)library_cardszlibrary_stats.htmlzLibrary Statsr
  )r   r  rP   rR   HOME_LIBRARY_CARDSget_library_statsrg   )r   ra   r  r  r  s        rf   library_statszWebInterface.library_statsr  sE     #..088!33-3P
,@^hiir   c                    	 t        j                         }|j                  ||      }|rd|v rt	        d|d         S t        j                  d       t	        dd       S # t        $ r}t	        dd       cY d }~S d }~ww xY w)N)count
media_typezrecently_added.htmlr   recently_addedz/Unable to retrieve data for get_recently_added.)r&   r   get_recently_added_detailsIOErrorrg   r   r   )r   r  r  ra   r   r   re   s          rf   get_recently_addedzWebInterface.get_recently_added}  s    	R$//1K ;;%T^;_F &&0!0EFScLdeeKKIJ!0EDQQ  	R!0EDQQ	Rs   'A! !	B*A<6B<Bc                 p    t        j                  t        j                        j	                          dddS )z& Regroup play history in the database.targetr   zHRegrouping play history started. Check the logs to monitor any problems.r   )	threadingThreadr   regroup_historystartr   s     rf   r!  zWebInterface.regroup_history  s4     	 2 B BCIIK#eg 	gr   c                 B    t        j                         }|rdddS dddS )z9 Flush out all of the temporary sessions in the database.r   zTemporary sessions flushed.r   r   zFlush sessions failed.)r   delete_sessionsr   ra   r   s      rf   delete_temp_sessionsz!WebInterface.delete_temp_sessions  s-     ))+'4QRR%2JKKr   c                 B    t        j                         }|rdddS dddS )z; Flush out all of the recently added items in the database.r   zRecently added flushed.r   r   zFlush recently added failed.)r   delete_recently_addedr%  s      rf   r(  z"WebInterface.delete_recently_added  s-     //1'4MNN%2PQQr   c                     t        dd      S )Nzlibraries.html	Librariesr`   r   rg   r   s     rf   r   zWebInterface.libraries  s     ,<KPPr   get_libraries_tablec                     |j                  d      sg d}t        ||d      |d<   t        j                  |d      }t	        j
                         }|j                  ||      }|S )a
   Get the data on the Tautulli libraries table.

            ```
            Required parameters:
                None

            Optional parameters:
                grouping (int):                 0 or 1
                order_column (str):             "library_thumb", "section_name", "section_type", "count", "parent_count",
                                                "child_count", "last_accessed", "last_played", "plays", "duration"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "Movies"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 10,
                     "recordsFiltered": 10,
                     "data":
                        [{"child_count": 3745,
                          "content_rating": "TV-MA",
                          "count": 62,
                          "do_notify": 1,
                          "do_notify_created": 1,
                          "duration": 1578037,
                          "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
                          "histroy_row_id": 1128,
                          "is_active": 1,
                          "keep_history": 1,
                          "labels": [],
                          "last_accessed": 1462693216,
                          "last_played": "Game of Thrones - The Red Woman",
                          "library_art": "/:/resources/show-fanart.jpg",
                          "library_thumb": "/:/resources/show.png",
                          "live": 0,
                          "media_index": 1,
                          "media_type": "episode",
                          "originally_available_at": "2016-04-24",
                          "parent_count": 240,
                          "parent_media_index": 6,
                          "parent_title": "",
                          "plays": 772,
                          "rating_key": 153037,
                          "row_id": 1,
                          "section_id": 2,
                          "section_name": "TV Shows",
                          "section_type": "Show",
                          "server_id": "ds48g4r354a8v9byrrtr697g3g79w",
                          "thumb": "/library/metadata/153036/thumb/1462175062",
                          "year": 2016
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        	json_data)
)library_thumbFF)section_nameTT)section_typeTT)r  TT)parent_countTT)child_countTT)last_accessedTFlast_playedTTplaysTFdurationTFr1  Treturn_nonera   grouping)rw   r0   r   r   r   r*  get_datatables_list)r   r?  ra   
dt_columnslibrary_datalibrary_lists         rf   get_library_listzWebInterface.get_library_list  si    F zz+&	5J #8
N"[F;$$X4@ **,#77vPX7Yr   get_library_namesc                     t        j                         }|j                         }|r|S t        j                  d       |S )a:   Get a list of library sections and ids on the PMS.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"section_id": 1, "section_name": "Movies", "section_type": "movie"},
                     {"section_id": 7, "section_name": "Music", "section_type": "artist"},
                     {"section_id": 2, "section_name": "TV Shows", "section_type": "show"},
                     {...}
                     ]
            ```
        z1Unable to retrieve data for get_library_sections.)r   r*  get_sectionsr   r   r   ra   rB  r   s       rf   get_library_sectionsz!WebInterface.get_library_sections  s:    0 !**,**,MKKKLMr   c                 l    t        j                  d       t        j                         }|rdddS dddS )z& Manually refresh the libraries list. z(Manual libraries list refresh requested.r   zLibraries list refreshed.r   r   z!Unable to refresh libraries list.)r   r   r   refresh_librariesr%  s      rf   refresh_libraries_listz#WebInterface.refresh_libraries_list5  s:    
 	>?,,.'4OPP%2UVVr   c                    t        |      s#t        j                  t        j                        t        j
                  j                  t        j
                  j                  d}|r(	 t        j                         }|j                  |      }n$t        j                  d       t        ddd |      S t        dd||      S #  t        j                  d|z         t        ddd |      cY S xY w)N)get_file_sizesget_file_sizes_hold
section_idz5Unable to retrieve library details for section_id %s zlibrary.htmlLibrary)r`   r   r   r   z2Library page requested but no section_id received.)r5   r   r   rP   rU   rR   GET_FILE_SIZESGET_FILE_SIZES_HOLDr   r*  get_detailsr   r   rg   debug)r   rQ  ra   r   rB  library_detailss         rf   libraryzWebInterface.libraryB  s     %Z0''(8(899 %mm::#)==#D#D

 o(224".":":j":"Q
 LLMN!iVZcijjN)RajpqqoSV``a%N)Z^gmnns   '&C )C,c                     |r)t        j                         }|j                  |      }d}nd }d}t        dd|t        j
                  j                  |      S )NrP  r   An error occured.zedit_library.htmlzEdit Library)r`   r   r   	server_idstatus_message)r   r*  rU  rg   rP   rR   r   )r   rQ  ra   rB  r   r\  s         rf   edit_library_dialogz WebInterface.edit_library_dialogZ  s]     $..0L!---DFNF0N,?~#)V]]5Q5Qbpr 	rr   c                 $   |j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }|r-	 t        j                         }|j                  ||||||       y	y#  Y y
xY w)a   Update a library section on Tautulli.

            ```
            Required parameters:
                section_id (str):           The id of the Plex library section
                custom_thumb (str):         The URL for the custom library thumbnail
                custom_art (str):           The URL for the custom library background art
                keep_history (int):         0 or 1

            Optional parameters:
                None

            Returns:
                None
            ```
        custom_thumbr   
custom_art	do_notifyr   do_notify_createdkeep_history)rQ  r_  r`  ra  rb  rc  zSuccessfully updated library.zFailed to update library.N)rw   r   r*  
set_config)	r   rQ  ra   r_  r`  ra  rb  rc  rB  s	            rf   edit_libraryzWebInterface.edit_libraryh  s    ( zz."5ZZb1
JJ{A.	"JJ':A>zz.!43(224'':5A3=2;:K5A ( C 7 32s   +B Bc                     t        |      st        dd d      S |r't        j                         }|j	                  |      }nd }|rt        d|d      S t        j                  d       t        dd d      S )Nuser_watch_time_stats.htmlWatch Statsr`   r   r   rP  z5Unable to retrieve data for library_watch_time_stats.)r5   rg   r   r*  get_watch_time_statsr   r   r   rQ  ra   rB  r   s        rf   library_watch_time_statsz%WebInterface.library_watch_time_stats  s{     %Z0!0LSW_lmm$..0L!66*6MFF!0LSYanooKKOP!0LSW_lmmr   c                     t        |      st        dd d      S |r't        j                         }|j	                  |      }nd }|rt        d|d      S t        j                  d       t        dd d      S )Nlibrary_user_stats.htmlPlayer Statsri  rP  z/Unable to retrieve data for library_user_stats.)r5   rg   r   r*  get_user_statsr   r   rk  s        rf   library_user_statszWebInterface.library_user_stats  s{     %Z0!0IPT\jkk$..0L!00J0GFF!0IPV^lmmKKIJ!0IPT\jkkr   c                     t        |      st        dd d      S |r(t        j                         }|j	                  ||      }nd }|rt        d|d      S t        j                  d       t        dd d      S )Nuser_recently_watched.htmlRecently Watchedri  )rQ  limitz5Unable to retrieve data for library_recently_watched.)r5   rg   r   r*  get_recently_watchedr   r   )r   rQ  ru  ra   rB  r   s         rf   library_recently_watchedz%WebInterface.library_recently_watched  s~     %Z0!0LSW_qrr$..0L!66*TY6ZFF!0LSYasttKKOP!0LSW_qrrr   c                    t        |      st        dd d      S |r(t        j                         }|j	                  ||      }nd }|r|d   rt        d|d   d      S t        j                  d       t        dd d      S )Nzlibrary_recently_added.htmlzRecently Addedri  )rQ  r  r  z3Unable to retrieve data for library_recently_added.)r5   rg   r&   r   r  r   r   )r   rQ  ru  ra   r   r   s         rf   library_recently_addedz#WebInterface.library_recently_added  s     %Z0!0MTX`pqq$//1K ;;zY^;_FFf-.!0MTZ[kTl  uE  F  FKKMN!0MTX`pqqr   c                 
   |j                  d      s-|j                  d      dk(  rd|d<   g d}t        ||d      |d<   t        j                  |      rd}nd}t	        j
                         }|j                  |||||      }|S )	a
   Get the data on the Tautulli media info tables.

            ```
            Required parameters:
                section_id (str):               The id of the Plex library section, OR
                rating_key (str):               The grandparent or parent rating key

            Optional parameters:
                section_type (str):             "movie", "show", "artist", "photo"
                order_column (str):             "added_at", "sort_title", "container", "bitrate", "video_codec",
                                                "video_resolution", "video_framerate", "audio_codec", "audio_channels",
                                                "file_size", "last_played", "play_count"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "Thrones"
                refresh (str):                  "true" to refresh the media info table

            Returns:
                json:
                    {"draw": 1,
                     "last_refreshed": 1678734670,
                     "recordsTotal": 82,
                     "recordsFiltered": 82,
                     "filtered_file_size": 2616760056742,
                     "total_file_size": 2616760056742,
                     "data":
                        [{"added_at": "1403553078",
                          "audio_channels": "",
                          "audio_codec": "",
                          "bitrate": "",
                          "container": "",
                          "file_size": 253660175293,
                          "grandparent_rating_key": "",
                          "last_played": 1462380698,
                          "media_index": "1",
                          "media_type": "show",
                          "parent_media_index": "",
                          "parent_rating_key": "",
                          "play_count": 15,
                          "rating_key": "1219",
                          "section_id": 2,
                          "section_type": "show",
                          "sort_title": "Game of Thrones",
                          "thumb": "/library/metadata/1219/thumb/1436265995",
                          "title": "Game of Thrones",
                          "video_codec": "",
                          "video_framerate": "",
                          "video_resolution": "",
                          "year": "2011"
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  order_columnr   
sort_title))added_atTF)r|  TT)	containerTT)bitrateTT)video_codecTT)video_resolutionTT)video_framerateTT)audio_codecTT)audio_channelsTT	file_sizeTFr7  TF)
play_countTFTF)rQ  r2  
rating_keyrefreshra   )rw   r0   r   r   r   r*  get_datatables_media_info)	r   rQ  r2  r  r  ra   rA  rB  r   s	            rf   get_library_media_infoz#WebInterface.get_library_media_info  s    @ zz+&zz.)W4)5~&7J #8
L"YF;W%GG **,77:EQCM@G?E	 8 G r   get_collections_tablec                 ~    |j                  d      sg d}t        ||d      |d<   t        j                  dd|i|}|S )a   Get the data on the Tautulli collections tables.

            ```
            Required parameters:
                section_id (str):               The id of the Plex library section

            Optional parameters:
                None

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 5,
                     "data":
                        [...]
                     }
            ```
        r/  ))	titleSortTT)collectionModeTT)collectionSortTT)
childCountTFr  rQ  rJ   )rw   r0   r   get_collections_list)r   rQ  ra   rA  r   s        rf   r  z!WebInterface.get_collections_list:  sI    2 zz+&7J #8
K"XF;//P:PPr   get_playlists_tablec                     |j                  d      sg d}t        ||d      |d<   t        j                  d||d|}|S )a   Get the data on the Tautulli playlists tables.

            ```
            Required parameters:
                section_id (str):               The section id of the Plex library, OR
                user_id (str):                  The user id of the Plex user

            Optional parameters:
                None

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 5,
                     "data":
                        [...]
                     }
            ```
        r/  )r   TT)	leafCountTT)r;  TTr   )rQ  user_idrJ   )rw   r0   r   get_playlists_list)r   rQ  r  ra   rA  r   s         rf   r  zWebInterface.get_playlists_list_  sU    4 zz+&4J #8
G"TF;-- 86=8068 r   c                    t         j                  j                  }t        |d         }t        |d         }t	        j
                  |      }t	        j
                  |      }|r||vs|r||vr|r|j                  |       n|r|j                  |       t        |      t        |      dt         j                  _        t        j                         }|j                  ||      }|r|j                  |       n|r|j                  |       t        |      t        |      dt         j                  _        d|iS d}d|iS )Nsection_idsrating_keys)r  r  )rQ  r  Fr   )rP   rR   rT  rp   r   cast_to_intr}   listr   r*  get_media_info_file_sizesremove)	r   rQ  r  ra   rO  r  r  rB  r   s	            rf   r  z&WebInterface.get_media_info_file_sizes  s,    %mm??-m<=-m<=((4
((4
:[8jZ_jMj
+
+@D[@Qbfgrbs0tFMM-$..0L!;;zGQ < SF "":."":.@D[@Qbfgrbs0tFMM- 6"" F6""r   c                     t        j                  |      }|rBt        j                         }|j	                  ||      }|r|S t        j                  d       |S t        j                  d       y)ae   Get a library's details.

            ```
            Required parameters:
                section_id (str):               The id of the Plex library section

            Optional parameters:
                include_last_accessed (bool):   True to include the last_accessed value for the library.

            Returns:
                json:
                    {"child_count": null,
                     "count": 887,
                     "deleted_section": 0,
                     "do_notify": 1,
                     "do_notify_created": 1,
                     "is_active": 1,
                     "keep_history": 1,
                     "last_accessed": 1462693216,
                     "library_art": "/:/resources/movie-fanart.jpg",
                     "library_thumb": "/:/resources/movie.png",
                     "parent_count": null,
                     "row_id": 1,
                     "section_id": 1,
                     "section_name": "Movies",
                     "section_type": "movie",
                     "server_id": "ds48g4r354a8v9byrrtr697g3g79w"
                     }
            ```
        )rQ  include_last_accessedz(Unable to retrieve data for get_library.z5Library details requested but no section_id received.N)r   r   r   r*  rU  r   r   )r   rQ  r  ra   rB  rW  s         rf   get_libraryzWebInterface.get_library  sl    F !( 1 12G H$..0L*66*Mb 7 dO&&FG&&KKOPr   c                     t        j                  |d      }|rCt        j                         }|j	                  |||      }|r|S t        j                  d       |S t        j                  d       y)a   Get a library's watch time statistics.

            ```
            Required parameters:
                section_id (str):       The id of the Plex library section

            Optional parameters:
                grouping (int):         0 or 1
                query_days (str):       Comma separated days, e.g. "1,7,30,0"

            Returns:
                json:
                    [{"query_days": 1,
                      "total_plays": 0,
                      "total_time": 0
                      },
                     {"query_days": 7,
                      "total_plays": 3,
                      "total_time": 15694
                      },
                     {"query_days": 30,
                      "total_plays": 35,
                      "total_time": 63054
                      },
                     {"query_days": 0,
                      "total_plays": 508,
                      "total_time": 1183080
                      }
                     ]
            ```
        Tr<  )rQ  r?  
query_daysz9Unable to retrieve data for get_library_watch_time_stats.z>Library watch time stats requested but no section_id received.N)r   r   r   r*  rj  r   r   )r   rQ  r?  r  ra   rB  r   s          rf   get_library_watch_time_statsz)WebInterface.get_library_watch_time_stats  sm    H $$X4@$..0L!66*W_BL 7 NFWXKKXYr   c                     t        j                  |d      }|rBt        j                         }|j	                  ||      }|r|S t        j                  d       |S t        j                  d       y)a/   Get a library's user statistics.

            ```
            Required parameters:
                section_id (str):       The id of the Plex library section

            Optional parameters:
                grouping (int):         0 or 1

            Returns:
                json:
                    [{"friendly_name": "Jon Snow",
                      "total_plays": 170,
                      "total_time": 349618,
                      "user_id": 133788,
                      "user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
                      "username": "LordCommanderSnow"
                      },
                     {"friendly_name": "DanyKhaleesi69",
                      "total_plays": 42,
                      "total_time": 50185,
                      "user_id": 8008135,
                      "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
                      "username: "DanyKhaleesi69"
                      },
                     {...},
                     {...}
                     ]
            ```
        Tr<  )rQ  r?  z3Unable to retrieve data for get_library_user_stats.z8Library user stats requested but no section_id received.N)r   r   r   r*  rp  r   r   )r   rQ  r?  ra   rB  r   s         rf   get_library_user_statsz#WebInterface.get_library_user_stats  se    F $$X4@$..0L!00JQY0ZFQRKKRSr   c                     |r|s|r5t        j                         }|j                  |||d      }|rdddS dddS dddS )	a   Delete all Tautulli history for a specific library.

            ```
            Required parameters:
                server_id (str):        The Plex server identifier of the library section
                section_id (str):       The id of the Plex library section

            Optional parameters:
                row_ids (str):          Comma separated row ids to delete, e.g. "2,3,8"

            Returns:
                None
            ```
        T)r[  rQ  row_ids
purge_onlyr   zDeleted library history.r   r   z$Failed to delete library(s) history.0No server id and section id or row ids received.r   r*  deleter   r[  rQ  r  ra   rB  r   s          rf   delete_all_library_historyz'WebInterface.delete_all_library_history8  s[    & *$..0L"))I*^erv)wG"+8RSS")6\]]%2deer   c                     |r|s|r4t        j                         }|j                  |||      }|rdddS dddS dddS )a   Delete a library section from Tautulli. Also erases all history for the library.

            ```
            Required parameters:
                server_id (str):        The Plex server identifier of the library section
                section_id (str):       The id of the Plex library section

            Optional parameters:
                row_ids (str):          Comma separated row ids to delete, e.g. "2,3,8"

            Returns:
                None
            ```
        )r[  rQ  r  r   zDeleted library.r   r   zFailed to delete library(s).r  r  r  s          rf   delete_libraryzWebInterface.delete_libraryU  sX    & *$..0L"))I*^e)fG"+8JKK")6TUU%2deer   c                     t        j                         }|j                  ||      }|r|rd|z  }n|rd|z  }ddz  dS dddS )	at   Restore a deleted library section to Tautulli.

            ```
            Required parameters:
                section_id (str):       The id of the Plex library section
                section_name (str):     The name of the Plex library section

            Optional parameters:
                None

            Returns:
                None
            ```
        )rQ  r1  zsection_id %szsection_name %sr   zRe-added library with %s.r   r   z=Unable to re-add library. Invalid section_id or section_name.)r   r*  undelete)r   rQ  r1  ra   rB  r   msgs          rf   undelete_libraryzWebInterface.undelete_libraryr  sb    & !**,&&*<&X$z1',6'4ORU4UVV!.mnnr   c                     t         j                  j                  }t        |d         }||vr3|r,t	        j
                         }|j                  |      }|rd|iS ddiS yddiS )a2   Delete the media info table cache for a specific library.

            ```
            Required parameters:
                section_id (str):       The id of the Plex library section

            Optional parameters:
                None

            Returns:
                None
            ```
        r  rP  r   no data receivedz8Cannot delete media info cache while getting file sizes.N)rP   rR   rT  rp   r   r*  delete_media_info_cache)r   rQ  ra   rO  r  rB  
delete_rows          rf   r  z$WebInterface.delete_media_info_cache  s}    $ %mm??-m<=[((224)AAZAX
%z22!#566 
 YZZr   c                 ^    t        j                         }|j                         }|rd|iS ddiS )Nr   z7Unable to delete duplicate libraries from the database.)r   r*  delete_duplicate_librariesrH  s       rf   r  z'WebInterface.delete_duplicate_libraries  s:     !**,88:v&&XYYr   c                     t        dd      S )Nz
users.htmlUsersr+  r,  r   s     rf   r'   zWebInterface.users  s     LHHr   get_users_tablec                     |j                  d      sg d}t        ||d      |d<   t        j                  |d      }t	        j
                         }|j                  ||      }|S )ap
   Get the data on Tautulli users table.

            ```
            Required parameters:
                None

            Optional parameters:
                grouping (int):                 0 or 1
                order_column (str):             "user_thumb", "friendly_name", "last_seen", "ip_address", "platform",
                                                "player", "last_played", "plays", "duration"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "Jon Snow"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 10,
                     "recordsFiltered": 10,
                     "data":
                        [{"allow_guest": 1,
                          "do_notify": 1,
                          "duration": 2998290,
                          "email": "Jon.Snow.1337@CastleBlack.com",
                          "friendly_name": "Jon Snow",
                          "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
                          "history_row_id": 1121,
                          "ip_address": "xxx.xxx.xxx.xxx",
                          "is_active": 1,
                          "keep_history": 1,
                          "last_played": "Game of Thrones - The Red Woman",
                          "last_seen": 1462591869,
                          "live": 0,
                          "media_index": 1,
                          "media_type": "episode",
                          "originally_available_at": "2016-04-24",
                          "parent_media_index": 6,
                          "parent_title": "",
                          "platform": "Chrome",
                          "player": "Plex Web (Chrome)",
                          "plays": 487,
                          "rating_key": 153037,
                          "row_id": 1,
                          "thumb": "/library/metadata/153036/thumb/1462175062",
                          "title": "Jon Snow",
                          "transcode_decision": "transcode",
                          "user_id": 133788,
                          "user_thumb": "https://plex.tv/users/568gwwoib5t98a3a/avatar",
                          "username": "LordCommanderSnow",
                          "year": 2016
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  ))
user_thumbFFfriendly_nameTT)usernameTTr  )emailTT	last_seenTF
ip_addressTTplatformTTplayerTTr  r8  r:  r  Tr<  r>  )rw   r0   r   r   r'   r  r@  )r   r?  ra   rA  	user_data	user_lists         rf   get_user_listzWebInterface.get_user_list  se    D zz+&5J #8
O"\F;$$X4@KKM	11(1S	r   c                 l    t        j                  d       t        j                         }|rdddS dddS )z" Manually refresh the users list. z$Manual users list refresh requested.r   zUsers list refreshed.r   r   zUnable to refresh users list.)r   r   r'   refresh_usersr%  s      rf   refresh_users_listzWebInterface.refresh_users_list  s:    
 	:;$$&'4KLL%2QRRr   c                 n   t        |      s#t        j                  t        j                        |r(	 t        j                         }|j                  |      }n#t        j                  d       t        ddd       S t        dd|      S #  t        j                  d|z         t        ddd       cY S xY w)Nr  z/Unable to retrieve user details for user_id %s z	user.htmlUserr
  z,User page requested but no user_id received.)r4   r   r   rP   rU   r'   r  rU  r   r   rg   rV  )r   r  ra   r  user_detailss        rf   userzWebInterface.user*  s     "'*''(8(899Z!KKM	(44W4E
 LLGH!6PTUUKvLYYZMPWWX%KvTXYYs   &B
 
(B4c                 ~    |r)t        j                         }|j                  |      }d}nd }d}t        dd||      S )Nr  r   rZ  zedit_user.htmlz	Edit User)r`   r   r   r\  )r'   r  rU  rg   )r   r  r  ra   r  r   r\  s          rf   edit_user_dialogzWebInterface.edit_user_dialog=  sH     I**7*;FNF0N,<KV\m{||r   c                 2   |j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }|j                  dd      }|r0	 t        j                         }|j                  ||||||       d	}	|	S y#  d
}	|	cY S xY w)a   Update a user on Tautulli.

            ```
            Required parameters:
                user_id (str):              The id of the Plex user
                friendly_name(str):         The friendly name of the user
                custom_thumb (str):         The URL for the custom user thumbnail
                keep_history (int):         0 or 1
                allow_guest (int):          0 or 1

            Optional paramters:
                None

            Returns:
                None
            ```
        r  r   r_  ra  r   rc  allow_guest)r  r  r_  ra  rc  r  zSuccessfully updated user.zFailed to update user.N)rw   r'   r  rd  )
r   r  ra   r  r_  ra  rc  r  r  r\  s
             rf   	edit_userzWebInterface.edit_userJ  s    * 

?B7zz."5JJ{A.	zz.!4jj2&!KKM	$$W3@2>/82>1< % > ">%% &!9%%s   .B Bc                     t        |      st        dd d      S |s|r't        j                         }|j	                  |      }nd }|rt        d|d      S t        j                  d       t        dd d      S )Nrg  rh  ri  r  z2Unable to retrieve data for user_watch_time_stats.)r4   rg   r'   r  rj  r   r   r   r  r  ra   r  r   s         rf   user_watch_time_statsz"WebInterface.user_watch_time_statst  sz     "'*!0LSW_lmmdI33G3DFF!0LSYanooKKLM!0LSW_lmmr   c                     t        |      st        dd d      S |s|r't        j                         }|j	                  |      }nd }|rt        d|d      S t        j                  d       t        dd d      S )Nzuser_player_stats.htmlro  ri  r  z.Unable to retrieve data for user_player_stats.)r4   rg   r'   r  get_player_statsr   r   r  s         rf   user_player_statszWebInterface.user_player_stats  sw     "'*!0Ht[ijjdI///@FF!0Hv]kllKKHI!0Ht[ijjr   c                     t        |      st        dd d      S |s|r(t        j                         }|j	                  ||      }nd }|rt        d|d      S t        j                  d       t        dd d      S )Nrs  rt  ri  )r  ru  z6Unable to retrieve data for get_user_recently_watched.)r4   rg   r'   r  rv  r   r   )r   r  r  ru  ra   r  r   s          rf   get_user_recently_watchedz&WebInterface.get_user_recently_watched  s|     "'*!0LSW_qrrdI33G53QFF!0LSYasttKKPQ!0LSW_qrrr   c                     |j                  d      sg d}t        ||d      |d<   t        j                         }|j	                  ||      }|S )a   Get the data on Tautulli users IP table.

            ```
            Required parameters:
                user_id (str):                  The id of the Plex user

            Optional parameters:
                order_column (str):             "last_seen", "first_seen", "ip_address", "platform",
                                                "player", "last_played", "play_count"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "xxx.xxx.xxx.xxx"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 2344,
                     "recordsFiltered": 10,
                     "data":
                        [{"friendly_name": "Jon Snow",
                          "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
                          "id": 1121,
                          "ip_address": "xxx.xxx.xxx.xxx",
                          "last_played": "Game of Thrones - The Red Woman",
                          "last_seen": 1462591869,
                          "first_seen": 1583968210,
                          "live": 0,
                          "media_index": 1,
                          "media_type": "episode",
                          "originally_available_at": "2016-04-24",
                          "parent_media_index": 6,
                          "parent_title": "",
                          "platform": "Chrome",
                          "play_count": 149,
                          "player": "Plex Web (Chrome)",
                          "rating_key": 153037,
                          "thumb": "/library/metadata/153036/thumb/1462175062",
                          "transcode_decision": "transcode",
                          "user_id": 133788,
                          "year": 2016
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  )r  )
first_seenTFr  r  r  r6  )r  TTr  )r  ra   )rw   r0   r'   r  get_datatables_unique_ips)r   r  ra   rA  r  historys         rf   get_user_ipszWebInterface.get_user_ips  sR    p zz+&6J #8
K"XF;KKM	55gf5Ur   c                     |j                  d      sg d}t        ||d      |d<   t               }t        j                         }|j                  |||      }|S )a   Get the data on Tautulli user login table.

            ```
            Required parameters:
                user_id (str):                  The id of the Plex user

            Optional parameters:
                order_column (str):             "date", "time", "ip_address", "host", "os", "browser"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "xxx.xxx.xxx.xxx"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 2344,
                     "recordsFiltered": 10,
                     "data":
                        [{"browser": "Safari 7.0.3",
                          "current": false,
                          "expiry": "2021-06-30 18:48:03",
                          "friendly_name": "Jon Snow",
                          "host": "http://plexpy.castleblack.com",
                          "ip_address": "xxx.xxx.xxx.xxx",
                          "os": "Mac OS X",
                          "row_id": 1,
                          "timestamp": 1462591869,
                          "user": "LordCommanderSnow",
                          "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A",
                          "user_group": "guest",
                          "user_id": 133788
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  )	timestampTFr  )hostTT)rL   TT)browserTTr  )r  	jwt_tokenra   )rw   r0   r:   r'   r  get_datatables_user_login)r   r  ra   rA  r  r  r  s          rf   get_user_loginszWebInterface.get_user_logins  sd    ^ zz+&3J
 #8
K"XF;!O	KKM	55g@I=C 6 E r   c                 f    t        j                         }|j                  |      }|rdddS dddS )a&   Logout Tautulli user sessions.

            ```
            Required parameters:
                row_ids (str):          Comma separated row ids to sign out, e.g. "2,3,8"

            Optional parameters:
                None

            Returns:
                None
            ```
        r  r   zUsers session logged out.r   r   zUnable to logout user session.)r'   r  clear_user_login_token)r   r  ra   r  r   s        rf   logout_user_sessionz WebInterface.logout_user_session3  s=    $ KKM	11'1B'4OPP%2RSSr   c                     t        j                  |      }|rBt        j                         }|j	                  ||      }|r|S t        j                  d       |S t        j                  d       y)a   Get a user's details.

            ```
            Required parameters:
                user_id (str):              The id of the Plex user

            Optional parameters:
                include_last_seen (bool):   True to include the last_seen value for the user.

            Returns:
                json:
                    {"allow_guest": 1,
                     "deleted_user": 0,
                     "do_notify": 1,
                     "email": "Jon.Snow.1337@CastleBlack.com",
                     "friendly_name": "Jon Snow",
                     "is_active": 1,
                     "is_admin": 0,
                     "is_allow_sync": 1,
                     "is_home_user": 1,
                     "is_restricted": 0,
                     "keep_history": 1,
                     "last_seen": 1462591869,
                     "row_id": 1,
                     "shared_libraries": ["10", "1", "4", "5", "15", "20", "2"],
                     "user_id": 133788,
                     "user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
                     "username": "LordCommanderSnow"
                     }
            ```
        )r  include_last_seenz%Unable to retrieve data for get_user.z/User details requested but no user_id received.N)r   r   r'   r  rU  r   r   )r   r  r  ra   r  r  s         rf   get_userzWebInterface.get_userM  si    H $--.?@I$00CT 1 VL##CD##KKIJr   c                     t        j                  |d      }|rCt        j                         }|j	                  |||      }|r|S t        j                  d       |S t        j                  d       y)a   Get a user's watch time statistics.

            ```
            Required parameters:
                user_id (str):          The id of the Plex user

            Optional parameters:
                grouping (int):         0 or 1
                query_days (str):       Comma separated days, e.g. "1,7,30,0"

            Returns:
                json:
                    [{"query_days": 1,
                      "total_plays": 0,
                      "total_time": 0
                      },
                     {"query_days": 7,
                      "total_plays": 3,
                      "total_time": 15694
                      },
                     {"query_days": 30,
                      "total_plays": 35,
                      "total_time": 63054
                      },
                     {"query_days": 0,
                      "total_plays": 508,
                      "total_time": 1183080
                      }
                     ]
            ```
        Tr<  )r  r?  r  z6Unable to retrieve data for get_user_watch_time_stats.8User watch time stats requested but no user_id received.N)r   r   r'   r  rj  r   r   )r   r  r?  r  ra   r  r   s          rf   get_user_watch_time_statsz&WebInterface.get_user_watch_time_stats~  sd    H $$X4@I33Ghcm3nFTUKKRSr   c                     t        j                  |d      }|rBt        j                         }|j	                  ||      }|r|S t        j                  d       |S t        j                  d       y)a   Get a user's player statistics.

            ```
            Required parameters:
                user_id (str):          The id of the Plex user

            Optional parameters:
                grouping (int):         0 or 1

            Returns:
                json:
                    [{"platform": "Chrome",
                      "platform_name": "chrome",
                      "player_name": "Plex Web (Chrome)",
                      "result_id": 1,
                      "total_plays": 170,
                      "total_time": 349618
                      },
                     {"platform": "Chromecast",
                      "platform_name": "chromecast",
                      "player_name": "Chromecast",
                      "result_id": 2,
                      "total_plays": 42,
                      "total_time": 50185
                      },
                     {...},
                     {...}
                     ]
            ```
        Tr<  )r  r?  z2Unable to retrieve data for get_user_player_stats.r  N)r   r   r'   r  r  r   r   )r   r  r?  ra   r  r   s         rf   get_user_player_statsz"WebInterface.get_user_player_stats  sa    F $$X4@I//(/SFPQKKRSr   c                 |    |s|r4t        j                         }|j                  ||d      }|rdddS dddS dddS )	aa   Delete all Tautulli history for a specific user.

            ```
            Required parameters:
                user_id (str):          The id of the Plex user

            Optional parameters:
                row_ids (str):          Comma separated row ids to delete, e.g. "2,3,8"

            Returns:
                None
            ```
        T)r  r  r  r   zDeleted user history.r   r   z!Failed to delete user(s) history.No user id or row ids received.r'   r  r  r   r  r  ra   r  r   s         rf   delete_all_user_historyz$WebInterface.delete_all_user_history  sS    $ gI&&wTX&YG"+8OPP")6YZZ%2STTr   c                 z    |s|r3t        j                         }|j                  ||      }|rdddS dddS dddS )as   Delete a user from Tautulli. Also erases all history for the user.

            ```
            Required parameters:
                user_id (str):          The id of the Plex user

            Optional parameters:
                row_ids (str):          Comma separated row ids to delete, e.g. "2,3,8"

            Returns:
                None
            ```
        )r  r  r   zDeleted user.r   r   zFailed to delete user(s).r   r  r  s         rf   delete_userzWebInterface.delete_user  sO    $ gI&&w&HG"+HH")6QRR%2STTr   c                     t        j                         }|j                  ||      }|r|rd|z  }n|rd|z  }ddz  dS dddS )	aW   Restore a deleted user to Tautulli.

            ```
            Required parameters:
                user_id (str):          The id of the Plex user
                username (str):         The username of the Plex user

            Optional parameters:
                None

            Returns:
                None
            ```
        )r  r  z
user_id %szusername %sr   zRe-added user with %s.r   r   z3Unable to re-add user. Invalid user_id or username.)r'   r  r  )r   r  r  ra   r  r   r  s          rf   undelete_userzWebInterface.undelete_user  s^    & KKM	##Gh#G!G+#h.'4Ls4RSS!.cddr   c                 B    dt         j                  i}t        dd|      S )Ndatabase_is_importingzhistory.htmlHistoryr   )r   IS_IMPORTINGrg   r   s      rf   r  zWebInterface.history7  s(     $X%:%:
 N)TZ[[r   c                    |j                  d      sg d}t        ||d      |d<   t        j                  |d      }t        j                  |d      }g }|r+t        j                  |      }|r@|j                  d|g       n,|r*t        j                  |      }|r|j                  d|g       d|v r|j                  d	      d
v r|j                  d      rt        j                         }|j                  |j                  d      |j                  d	            }	|	d   D 
cg c]  }
|
d   	 }}
|j                  d|g       |j                  d|g       |j                  d|g       n:t        j                  |j                  dd            }|r|j                  d|g       d|v r:t        j                  |j                  dd            }|r|j                  d|g       d|v r:t        j                  |j                  dd            }|r|j                  d|g       d|v r:t        j                  |j                  dd            }|r|j                  d|g       d|v r:t        j                  |j                  dd            }|r|j                  d|g       d|v r:t        j                  |j                  dd            }|r|j                  d|g       d|v r:t        j                  |j                  dd            }|r|j                  d|g       d|v r:t        j                  |j                  dd            }|r|j                  d|g       d	|v r>t        j                  |j                  d	d            }|rd |vr|j                  d!|g       d"|v r>t        j                  |j                  d"d            }|rd |vr|j                  d#|g       d$|v r`t        j                  |j                  d$d      j                  d%      d&         }|r'|j                  d'|D cg c]
  }d(|z   d)z    c}g       t        j                         }|j                  ||||*      }|S c c}
w c c}w )+a   Get the Tautulli history.

            ```
            Required parameters:
                None

            Optional parameters:
                grouping (int):                 0 or 1
                include_activity (int):         0 or 1
                user (str):                     "Jon Snow"
                user_id (int):                  133788
                rating_key (int):               4348
                parent_rating_key (int):        544
                grandparent_rating_key (int):   351
                start_date (str):               History for the exact date, "YYYY-MM-DD"
                before (str):                   History before and including the date, "YYYY-MM-DD"
                after (str):                    History after and including the date, "YYYY-MM-DD"
                section_id (int):               2
                media_type (str):               "movie", "episode", "track", "live", "collection", "playlist"
                transcode_decision (str):       "direct play", "copy", "transcode",
                guid (str):                     Plex guid for an item, e.g. "com.plexapp.agents.thetvdb://121361/6/1"
                order_column (str):             "date", "friendly_name", "ip_address", "platform", "player",
                                                "full_title", "started", "paused_counter", "stopped", "duration"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "Thrones"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 1000,
                     "recordsFiltered": 250,
                     "total_duration": "42 days 5 hrs 18 mins",
                     "filter_duration": "10 hrs 12 mins",
                     "data":
                        [{"date": 1462687607,
                          "friendly_name": "Mother of Dragons",
                          "full_title": "Game of Thrones - The Red Woman",
                          "grandparent_rating_key": 351,
                          "grandparent_title": "Game of Thrones",
                          "original_title": "",
                          "group_count": 1,
                          "group_ids": "1124",
                          "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
                          "ip_address": "xxx.xxx.xxx.xxx",
                          "live": 0,
                          "location": "wan",
                          "machine_id": "lmd93nkn12k29j2lnm",
                          "media_index": 17,
                          "media_type": "episode",
                          "originally_available_at": "2016-04-24",
                          "parent_media_index": 7,
                          "parent_rating_key": 544,
                          "parent_title": "",
                          "paused_counter": 0,
                          "percent_complete": 84,
                          "platform": "Windows",
                          "play_duration": 263,
                          "product": "Plex for Windows",
                          "player": "Castle-PC",
                          "rating_key": 4348,
                          "reference_id": 1123,
                          "relayed": 0,
                          "row_id": 1124,
                          "secure": 1,
                          "session_key": null,
                          "started": 1462688107,
                          "state": null,
                          "stopped": 1462688370,
                          "thumb": "/library/metadata/4348/thumb/1462414561",
                          "title": "The Red Woman",
                          "transcode_decision": "transcode",
                          "user": "DanyKhaleesi69",
                          "user_id": 8008135,
                          "watched_status": 0,
                          "year": 2016
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  ))dateTFr  r  r  )productTTr  )
full_titleTT)startedTF)paused_counterTF)stoppedTFr:  )watched_statusFFr  Tr<  zsession_history.user_idzsession_history.userr  r  )
collectionplaylistr  r  children_listz&session_history_metadata.rating_key ORz-session_history_metadata.parent_rating_key ORz2session_history_metadata.grandparent_rating_key ORr   zsession_history.rating_keyparent_rating_keyz!session_history.parent_rating_keygrandparent_rating_keyz&session_history.grandparent_rating_key
start_datezAstrftime('%Y-%m-%d', datetime(started, 'unixepoch', 'localtime'))beforezCstrftime('%Y-%m-%d', datetime(started, 'unixepoch', 'localtime')) <afterzCstrftime('%Y-%m-%d', datetime(started, 'unixepoch', 'localtime')) >reference_idzsession_history.reference_idrQ  zsession_history.section_idallmedia_type_livetranscode_decisionz-session_history_media_info.transcode_decisionguidrE   r   zsession_history_metadata.guidzLIKE %)ra   custom_wherer?  include_activity)rw   r0   r   r   split_stripappendr&   r   get_item_childrenpopsplitr   r  get_datatables_history)r   r  r  r?  r$  ra   rA  r#  r   r   childr  r  r  r  r  r  rQ  r  r   r!  gr  r  s                           rf   get_historyzWebInterface.get_history@  se   x zz+&<J #8
F"SF;$$X4@",,-=4P))'2G##%>$HI&&t,D##%;T$BC6!zz,'+EE&**UaJb(335$66&**\BZgmgqgqr~g6  A@F@WXuu\2XX##%M{$[\##%TVa$bc##%Y[f$gh$00L"1MN
 '')Ez(RS&( ,,VZZ8KR-PQJ##%H*$UV#v- ,,VZZ8PRT-UVJ##%Mz$Z[6! ,,VZZb-IJJ##%hjt$uvv((Hb)ABF##%jlr$stf''

7B(?@E##%jlq$rsV#"..vzz."/MNL##%C\$RS6! ,,VZZb-IJJ##%A:$NO6! ,,VZZb-IJJe:5##%6
$CD6)!(!4!4VZZ@TVX5Y!Z!e3E&E##%TVh$ijV&&vzz&"'='C'CC'H'KLD##%DbfFg]^wQR{UXGXFg$hi"..055VR^?GZj 6 l k Y^ Ghs   #Q*(Q/
c                 l    t        j                         }|j                  ||      }t        dd||      S )Nzstream_data.htmlzStream Data)r`   r   r   r  )r   r  get_stream_detailsrg   )r   row_idr   r  ra   r  stream_datas          rf   get_stream_datazWebInterface.get_stream_data  s8     #..0"55fkJ,>mZelpqqr   r2  c                 d    d|v r|d   }t        j                         }|j                  ||      }|S )an   Get the stream details from history or current stream.

            ```
            Required parameters:
                row_id (int):       The row ID number for the history item, OR
                session_key (int):  The session key of the current stream

            Optional parameters:
                None

            Returns:
                json:
                    {"aspect_ratio": "2.35",
                     "audio_bitrate": 231,
                     "audio_channels": 6,
                     "audio_language": "English",
                     "audio_language_code": "eng",
                     "audio_codec": "aac",
                     "audio_decision": "transcode",
                     "bitrate": 2731,
                     "container": "mp4",
                     "current_session": "",
                     "grandparent_title": "",
                     "media_type": "movie",
                     "optimized_version": "",
                     "optimized_version_profile": "",
                     "optimized_version_title": "",
                     "original_title": "",
                     "pre_tautulli": "",
                     "quality_profile": "1.5 Mbps 480p",
                     "stream_audio_bitrate": 203,
                     "stream_audio_channels": 2,
                     "stream_audio_language": "English",
                     "stream_audio_language_code", "eng",
                     "stream_audio_codec": "aac",
                     "stream_audio_decision": "transcode",
                     "stream_bitrate": 730,
                     "stream_container": "mkv",
                     "stream_container_decision": "transcode",
                     "stream_subtitle_codec": "",
                     "stream_subtitle_decision": "",
                     "stream_video_bitrate": 527,
                     "stream_video_codec": "h264",
                     "stream_video_decision": "transcode",
                     "stream_video_dynamic_range": "SDR",
                     "stream_video_framerate": "24p",
                     "stream_video_height": 306,
                     "stream_video_resolution": "SD",
                     "stream_video_width": 720,
                     "subtitle_codec": "",
                     "subtitles": "",
                     "synced_version": "",
                     "synced_version_profile": "",
                     "title": "Frozen",
                     "transcode_hw_decoding": "",
                     "transcode_hw_encoding": "",
                     "video_bitrate": 2500,
                     "video_codec": "h264",
                     "video_decision": "transcode",
                     "video_dynamic_range": "SDR",
                     "video_framerate": "24p",
                     "video_height": 816,
                     "video_resolution": "1080",
                     "video_width": 1920
                     }
            ```
        id)r   r  r/  )r   r0  r   ra   r  r1  s         rf   get_stream_data_apiz WebInterface.get_stream_data_api  s;    R 6>D\F"..0"55fkJr   c                 z    t        j                  |      sd }t        j                  |      }t        dd|||      S )Nzip_address_modal.htmlzIP Address Details)r`   r   r   publicra   )r   is_valid_ipis_public_iprg   )r   r  ra   r7  s       rf   get_ip_address_detailsz#WebInterface.get_ip_address_detailsM  sC     "":.J%%j1,CK_#-fVM 	Mr   delete_historyc                 |    t        j                         }|r"t        j                  |      }|rdddS dddS dddS )a0   Delete history rows from Tautulli.

            ```
            Required parameters:
                row_ids (str):          Comma separated row ids to delete, e.g. "65,110,2,3645"

            Optional parameters:
                None

            Returns:
                None
            ```
        r  r   zDeleted history.r   r   zFailed to delete history.zNo row ids received.)r   r  r   delete_session_history_rows)r   r  ra   r  r   s        rf   delete_history_rowsz WebInterface.delete_history_rowsX  sL    $ #..0::7KG"+8JKK")6QRR%2HIIr   c                     t        dd      S )Nzgraphs.htmlGraphsr+  r,  r   s     rf   r   zWebInterface.graphsy  s     MJJr   c                 R    t        j                         }|j                  |      }|S )a   Get a list of all user and user ids.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"friendly_name": "Jon Snow", "user_id": 133788},
                     {"friendly_name": "DanyKhaleesi69", "user_id": 8008135},
                     {"friendly_name": "Tyrion Lannister", "user_id": 696969},
                     {...},
                    ]
            ```
        ra   )r'   r  get_user_names)r   ra   r  
user_namess       rf   rC  zWebInterface.get_user_names~  s(    0 KKM	--V-<
r   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )as   Get graph data by date.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["YYYY-MM-DD", "YYYY-MM-DD", ...]
                     "series":
                        [{"name": "Movies", "data": [...]}
                         {"name": "TV", "data": [...]},
                         {"name": "Music", "data": [...]},
                         {"name": "Live TV", "data": [...]}
                         ]
                     }
            ```
        Tr<  r  y_axisr  r?  z.Unable to retrieve data for get_plays_by_date.)r   r   r   r@  get_total_plays_per_dayr   r   r   r  r  rG  r?  ra   graphr   s           rf   get_plays_by_datezWebInterface.get_plays_by_date  s^    < $$X4@..*6<7>8@ / B
 MKKHIMr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )a   Get graph data by day of the week.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["Sunday", "Monday", "Tuesday", ..., "Saturday"]
                     "series":
                        [{"name": "Movies", "data": [...]}
                         {"name": "TV", "data": [...]},
                         {"name": "Music", "data": [...]},
                         {"name": "Live TV", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z3Unable to retrieve data for get_plays_by_dayofweek.)r   r   r   r@  get_total_plays_per_dayofweekr   r   rI  s           rf   get_plays_by_dayofweekz#WebInterface.get_plays_by_dayofweek  ^    < $$X4@44
<B=D>F 5 H
 MKKMNMr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )az   Get graph data by hour of the day.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["00", "01", "02", ..., "23"]
                     "series":
                        [{"name": "Movies", "data": [...]}
                         {"name": "TV", "data": [...]},
                         {"name": "Music", "data": [...]},
                         {"name": "Live TV", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z3Unable to retrieve data for get_plays_by_hourofday.)r   r   r   r@  get_total_plays_per_hourofdayr   r   rI  s           rf   get_plays_by_hourofdayz#WebInterface.get_plays_by_hourofday  rO  r   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )a~   Get graph data by month.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of months of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["Jan 2016", "Feb 2016", "Mar 2016", ...]
                     "series":
                        [{"name": "Movies", "data": [...]}
                         {"name": "TV", "data": [...]},
                         {"name": "Music", "data": [...]},
                         {"name": "Live TV", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z0Unable to retrieve data for get_plays_per_month.)r   r   r   r@  get_total_plays_per_monthr   r   r   r  rG  r  r?  ra   rJ  r   s           rf   get_plays_per_monthz WebInterface.get_plays_per_month	  s^    < $$X4@00J8>9@:B 1 D
 MKKJKMr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )a   Get graph data by top 10 platforms.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["iOS", "Android", "Chrome", ...]
                     "series":
                        [{"name": "Movies", "data": [...]}
                         {"name": "TV", "data": [...]},
                         {"name": "Music", "data": [...]},
                         {"name": "Live TV", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z:Unable to retrieve data for get_plays_by_top_10_platforms.)r   r   r   r@  #get_total_plays_by_top_10_platformsr   r   rU  s           rf   get_plays_by_top_10_platformsz*WebInterface.get_plays_by_top_10_platformsK	  s^    < $$X4@::jBHCJDL ; N
 MKKTUMr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )a   Get graph data by top 10 users.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["Jon Snow", "DanyKhaleesi69", "A Girl", ...]
                     "series":
                        [{"name": "Movies", "data": [...]}
                         {"name": "TV", "data": [...]},
                         {"name": "Music", "data": [...]},
                         {"name": "Live TV", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z6Unable to retrieve data for get_plays_by_top_10_users.)r   r   r   r@  get_total_plays_by_top_10_usersr   r   rU  s           rf   get_plays_by_top_10_usersz&WebInterface.get_plays_by_top_10_usersw	  s^    < $$X4@66*>D?F@H 7 J
 MKKPQMr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )aY   Get graph data by stream type by date.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["YYYY-MM-DD", "YYYY-MM-DD", ...]
                     "series":
                        [{"name": "Direct Play", "data": [...]}
                         {"name": "Direct Stream", "data": [...]},
                         {"name": "Transcode", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z5Unable to retrieve data for get_plays_by_stream_type.)r   r   r   r@  get_total_plays_per_stream_typer   r   rU  s           rf   get_plays_by_stream_typez%WebInterface.get_plays_by_stream_type	  s^    : $$X4@66*>D?F@H 7 J
 MKKOPMr   c                     t        j                         }|j                  ||      }|r|S t        j                  d       |S )aQ   Get graph data for concurrent streams by stream type by date.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                user_id (str):          Comma separated list of user id to filter the data

            Returns:
                json:
                    {"categories":
                        ["YYYY-MM-DD", "YYYY-MM-DD", ...]
                     "series":
                        [{"name": "Direct Play", "data": [...]}
                         {"name": "Direct Stream", "data": [...]},
                         {"name": "Transcode", "data": [...]},
                         {"name": "Max. Concurrent Streams", "data":  [...]}
                         ]
                     }
            ```
        )r  r  zBUnable to retrieve data for get_concurrent_streams_by_stream_type.)r   r@  ,get_total_concurrent_streams_per_stream_typer   r   )r   r  r  ra   rJ  r   s         rf   %get_concurrent_streams_by_stream_typez2WebInterface.get_concurrent_streams_by_stream_type	  s?    : CCzcjCkMKK\]Mr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )aP   Get graph data by source resolution.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["720", "1080", "sd", ...]
                     "series":
                        [{"name": "Direct Play", "data": [...]}
                         {"name": "Direct Stream", "data": [...]},
                         {"name": "Transcode", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z;Unable to retrieve data for get_plays_by_source_resolution.)r   r   r   r@  $get_total_plays_by_source_resolutionr   r   rU  s           rf   get_plays_by_source_resolutionz+WebInterface.get_plays_by_source_resolution	  ^    : $$X4@;;zCIDKEM < O
 MKKUVMr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )aP   Get graph data by stream resolution.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["720", "1080", "sd", ...]
                     "series":
                        [{"name": "Direct Play", "data": [...]}
                         {"name": "Direct Stream", "data": [...]},
                         {"name": "Transcode", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z;Unable to retrieve data for get_plays_by_stream_resolution.)r   r   r   r@  $get_total_plays_by_stream_resolutionr   r   rU  s           rf   get_plays_by_stream_resolutionz+WebInterface.get_plays_by_stream_resolution
  rf  r   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )al   Get graph data by stream type by top 10 users.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["Jon Snow", "DanyKhaleesi69", "A Girl", ...]
                     "series":
                        [{"name": "Direct Play", "data": [...]}
                         {"name": "Direct Stream", "data": [...]},
                         {"name": "Transcode", "data": [...]}
                        ]
                     }
            ```
        Tr<  rF  z<Unable to retrieve data for get_stream_type_by_top_10_users.)r   r   r   r@  get_stream_type_by_top_10_usersr   r   rU  s           rf   rk  z,WebInterface.get_stream_type_by_top_10_usersJ
  s^    : $$X4@66*>D?F@H 7 J
 MKKVWMr   c                     t        j                  |d      }t        j                         }|j	                  ||||      }|r|S t        j                  d       |S )ae   Get graph data by stream type by top 10 platforms.

            ```
            Required parameters:
                None

            Optional parameters:
                time_range (str):       The number of days of data to return
                y_axis (str):           "plays" or "duration"
                user_id (str):          Comma separated list of user id to filter the data
                grouping (int):         0 or 1

            Returns:
                json:
                    {"categories":
                        ["iOS", "Android", "Chrome", ...]
                     "series":
                        [{"name": "Direct Play", "data": [...]}
                         {"name": "Direct Stream", "data": [...]},
                         {"name": "Transcode", "data": [...]}
                         ]
                     }
            ```
        Tr<  rF  z@Unable to retrieve data for get_stream_type_by_top_10_platforms.)r   r   r   r@  #get_stream_type_by_top_10_platformsr   r   rU  s           rf   rm  z0WebInterface.get_stream_type_by_top_10_platformsu
  s^    : $$X4@::jBHCJDL ; N
 MKKZ[Mr   c                 x    |j                  d      rt        |d         st        ddd       S t        dd|      S )Nr  zhistory_table_modal.htmlzHistory Datar
  )rw   r4   rg   r   s     rf   history_table_modalz WebInterface.history_table_modal
  s@     ::i );F9<M)N!0JR`gkll,Fncijjr   c                     t        dd      S )Nz	sync.htmlzSynced Itemsr+  r,  r   s     rf   synczWebInterface.sync
  s     K~NNr   c                     |dk(  rd }t               r
t               }t        j                  t        j                  j
                        }|j                  ||      }|rd|i}|S t        j                  d       dg i}|S )Nnullr   r   user_id_filterr   z%Unable to retrieve data for get_sync.)	r3   r#   r   rP   rR   r   get_synced_itemsr   r   )r   r   r  ra   r   r   outputs          rf   get_synczWebInterface.get_sync
  s~    
 fG )+G--fmm&=&=>))ZPW)Xf%F
  KK?@b\Fr   delete_synced_itemc                 z    |r5|r3t        j                         }|j                  ||      }|rdddS dddS dddS )ak   Delete a synced item from a device.

            ```
            Required parameters:
                client_id (str):        The client ID of the device to delete from
                sync_id (str):          The sync ID of the synced item

            Optional parameters:
                None

            Returns:
                None
            ```
        )r   sync_idr   z!Synced item deleted successfully.r   r   zFailed to delete synced item.zMissing client ID and sync ID.)r#   r   delete_sync)r   r   r{  ra   r   r  s         rf   delete_sync_rowszWebInterface.delete_sync_rows
  sP    & mmoG ,,y',RJ"+8[\\")6UVV%2RSSr   c                 F    t        j                         }t        dd|      S )Nz	logs.htmlLog)r`   r   plex_log_files)r   list_plex_logsrg   )r   ra   r  s      rf   logszWebInterface.logs
  s"     $224KuUcddr   c           	         t        j                  |j                  d            }|j                  dd      }|d   }|d   }|d   d   d	   }|d   d   d
   }|d   d   }	dg }
g }|
j                  }|dk(  rt        j
                  }n&|dk(  rt        j                  }nt        j                  }t        t        j                  j                  t        j                  j                  |      dd      5 }|j                         D ]  }	 |j!                  dd      }|d   j!                  dd      d   j#                         }t        j$                  |j!                  dd      d   j'                  dd            } ||d   ||g        	 d d d        g d}||v r,||j/                  |      d  }|
D cg c]  }|d   |v s| }}n|
}|	r9|D cg c],  }|D ]%  }|	j1                         |j1                         v s$|' . }}}|dk(  rdn|dk(  rd|j3                  fd       |d k(  r|d d d!   }||||z    }t5        j6                  t+        |      t+        |
      |d"      S # t(        $ rl t+        |
      dz
  }t+        |      t+        |j-                  d            z
  }dd|z  z  t        j$                  ||d        z   }|
|   dxx   d|z   z  cc<   Y w xY w# 1 sw Y   WxY wc c}w c c}}w )#Nr/  )json_kwargs	log_levelr   r"  lengthorderr   columndirsearchvaluetautulli_apiplex_websocketrutf-8encodingz - rj   z :: : 
 z&nbsp;   z<br>)DEBUGINFOWARNINGERROR12c                     |    S r   rJ   )x
sortcolumns    rf   <lambda>z&WebInterface.get_log.<locals>.<lambda>!  s    AjM r   )keydescri   )recordsFilteredrecordsTotalr   )r   process_json_kwargsrw   r&  r   FILENAME_APIFILENAME_PLEX_WEBSOCKETFILENAMEr   rL   rM   rN   rP   rR   LOG_DIR	readlinesr)  stripsanitizereplace
IndexErrorlenlstripr   lowersortjsondumps)r   logfilera   r/  r  r"  r  r{  	order_dirsearch_valuefiltfilteredfar   r   ltemp_loglevel_and_timeloglvlr  tlnll
log_levelsrowr  rowsr  s                             @rf   get_logzWebInterface.get_log
  s    //FJJ{<ST	JJ{B/	'"8$ )!,X6g&q)%0	 *73
[[n$**H((55HH"'',,v}}44h?wW 	[\[[] -.WWUA->*3A6<<UAFqIOOQF!**1775!+<Q+?+G+Gb+QRC.q163?@	 ;

"#J$4$4Y$?$ABJ'+Ds1v/CDHDH'/lclF\EWEWEY]c]i]i]kEkllHl3JS J12"~Huv~/zz"8}I
  	= " d)a-BAQXXc]!33A!QU+g.>.>qu.EEBHQK6B;.K	 	$ E
 msJ   3KA>I&K1K+>K+*K09K0&A1KKKKK(c                 .   |j                  d      rd|d   j                         z   }t        |j                  dt        j                  j
                              }	 dt        j                  |d|      iS #  t        j                  d|z         g cY S xY w)a   Get the PMS logs.

            ```
            Required parameters:
                None

            Optional parameters:
                window (int):           The number of tail lines to return
                logfile (int):          The name of the Plex log file,
                                        e.g. "Plex Media Server", "Plex Media Scanner"

            Returns:
                json:
                    [["May 08, 2016 09:35:37",
                      "DEBUG",
                      "Auth: Came in with a super-token, authorization succeeded."
                      ],
                     [...],
                     [...]
                     ]
            ```
        log_typePlex Media windowr   T)r  parsedlog_filez%Unable to retrieve Plex log file '%'.)
rw   
capitalizeintrP   rR   PMS_LOGS_LINE_CAPr   get_log_tailr   r   )r   r  ra   r  s       rf   get_plex_logzWebInterface.get_plex_log.  s    6 ::j!#fZ&8&C&C&EEGVZZ&--*I*IJK	J336$Y`abb	KK?'IJIs   A6 6Bc                     |j                  d      sg d}t        ||d      |d<   t        j                         }|j	                  |      }|S )a[   Get the data on the Tautulli notification logs table.

            ```
            Required parameters:
                None

            Optional parameters:
                order_column (str):             "timestamp", "notifier_id", "agent_name", "notify_action",
                                                "subject_text", "body_text",
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "Telegram"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 1039,
                     "recordsFiltered": 163,
                     "data":
                        [{"agent_id": 13,
                          "agent_name": "telegram",
                          "body_text": "DanyKhaleesi69 started playing The Red Woman.",
                          "id": 1000,
                          "notify_action": "on_play",
                          "rating_key": 153037,
                          "session_key": 147,
                          "subject_text": "Tautulli (Winterfell-Server)",
                          "success": 1,
                          "timestamp": 1462253821,
                          "user": "DanyKhaleesi69",
                          "user_id": 8008135
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  )r  TT)notifier_idTT
agent_nameTTnotify_actionTTsubject_textTT	body_textTTr  rB  )rw   r0   r   r  get_notification_log)r   ra   rA  r  notification_logss        rf   r  z!WebInterface.get_notification_logT  sU    ^ zz+&5J #8
K"XF;"..0(==V=L  r   c                     |j                  d      sg d}t        ||d      |d<   t        j                         }|j	                  |      }|S )a;   Get the data on the Tautulli newsletter logs table.

            ```
            Required parameters:
                None

            Optional parameters:
                order_column (str):             "timestamp", "newsletter_id", "agent_name", "notify_action",
                                                "subject_text", "start_date", "end_date", "uuid"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "Telegram"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 1039,
                     "recordsFiltered": 163,
                     "data":
                        [{"agent_id": 0,
                          "agent_name": "recently_added",
                          "end_date": "2018-03-18",
                          "id": 7,
                          "newsletter_id": 1,
                          "notify_action": "on_cron",
                          "start_date": "2018-03-05",
                          "subject_text": "Recently Added to Plex (Winterfell-Server)! (2018-03-18)",
                          "success": 1,
                          "timestamp": 1462253821,
                          "uuid": "7fe4g65i"
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  )	r  )newsletter_idTTr  r  r  r  )r  TT)end_dateTT)uuidTTr  rB  )rw   r0   r   r  get_newsletter_log)r   ra   rA  r  newsletter_logss        rf   r  zWebInterface.get_newsletter_log  sT    \ zz+&0J #8
K"XF;"..0&999Hr   c                 l    t        j                         }|j                         }|rdnd}|rdnd}||dS )z Delete the Tautulli notification logs.

            ```
            Required paramters:
                None

            Optional parameters:
                None

            Returns:
                None
            ```
        r   r   zCleared notification logs.z"Failed to clear notification logs.r   )r   r  delete_notification_logr   ra   r  r   resr  s         rf   r  z$WebInterface.delete_notification_log  s?    $ #..0557!iw.4*:^#..r   c                 l    t        j                         }|j                         }|rdnd}|rdnd}||dS )z Delete the Tautulli newsletter logs.

            ```
            Required paramters:
                None

            Optional parameters:
                None

            Returns:
                None
            ```
        r   r   zCleared newsletter logs.z Failed to clear newsletter logs.r   )r   r  delete_newsletter_logr  s         rf   r  z"WebInterface.delete_newsletter_log  s?    $ #..0335!iw,2(8Z#..r   c                 l    t        j                         }|j                         }|rdnd}|rdnd}||dS )z Delete the Tautulli login logs.

            ```
            Required paramters:
                None

            Optional parameters:
                None

            Returns:
                None
            ```
        r   r   zCleared login logs.zFailed to clear login logs.r   )r'   r  delete_login_log)r   ra   r  r   r  r  s         rf   r  zWebInterface.delete_login_log  s<    $ KKM	++-!iw'-#3P#..r   c                    |dk(  rt         j                  }n&|dk(  rt         j                  }nt         j                  }	 t	        t
        j                  j                  t        j                  j                  |      d      j                          d}d|z  }t        j                  |       ||dS # t        $ r-}d}d|z  }t        j                  d|d	|d
       Y d }~6d }~ww xY w)Nr  r  wr   zCleared the %s file.r   zFailed to clear the %s file.zFailed to clear the z file: .r   )r   r  r  r  r   rL   rM   rN   rP   rR   r  closer   r[   r\   )r   r  ra   r   r   r  re   s          rf   delete_logszWebInterface.delete_logs  s     n$**H((55HH	Qfmm33X>DJJLF(83CKK !S11  	QF08;C8QOPP	Qs   A,B/ /	C%8#C  C%c                    t         j                   t         _        t         j                  t         j                  _        t         j                  j	                          t        j                  t         j                   t         j                  j                  t         j                         t        j                  dt         j                         t        j                  d       t        j                  t         j                  dz         )N)consolelog_dirverbosezVerbose toggled, set to %sz4If you read this message, debug logging is availabler  )rP   VERBOSErR   VERBOSE_LOGSr   r   
initLoggerQUIETr  r   rV  r   r   rU   r   s     rf   toggleVerbosezWebInterface.toggleVerbose4  s     $^^+%+^^"fll"2FMM<Q<Q[a[i[ij0&..AKL##F$4$4v$=>>r   c                     t        j                  d|j                  d      d   d|d|j                  d      d   j                  d      d   d|d		       y
)z0 Logs javascript errors from the web interface. z
WebUI :: /r   ri   r  z. (rE   r   :)zjs error logged.)r   r   
rpartition	partition)r   pager   filelinera   s         rf   log_js_errorszWebInterface.log_js_errorsA  sS     	T__S5I"5M6=6:ooc6J26N6X6XY\6]^_6`6:< 	= "r   c                    |dk(  rt         j                  }n&|dk(  rt         j                  }nt         j                  }	 t	        t
        j                  j                  t        j                  j                  |      dd      5 }d|j                         z  cd d d        S # 1 sw Y   y xY w# t        $ r
}Y d }~yd }~ww xY w)Nr  r  r  r  r  z<pre>%s</pre>zLog file not found.)r   r  r  r  r   rL   rM   rN   rP   rR   r  readr  )r   r  ra   r   r   re   s         rf   logFilezWebInterface.logFileK  s     n$**H((55HH	)bggll6==#8#8(CSSZ[ 2_`&12 2 2 	)(	)s0   AB+ B	B+ B($B+ (B+ +	B>9B>c                    i }t         j                  D ]-  }t        t        j                  |      ||j                         <   / t         j                  D ]6  }t        t        t        j                  |            ||j                         <   8 t        j                  j                  dk7  rd|d<   nd|d<   dD ]  }t        j                  ||         ||<    t        dd|      S )Nr       http_password)r   home_stats_cardshome_library_cardszsettings.htmlSettingsr   )r   SETTINGSgetattrrP   rR   r  CHECKED_SETTINGSr,   HTTP_PASSWORDr  r  rg   )r   ra   settings_dictsettingr  s        rf   settingszWebInterface.settings^  s      	MG-4V]]G-LM'--/*	M .. 	VG-4WV]]G5T-UM'--/*	V
 ==&&",-3M/*-/M/*N 	@C!%M#,>!?M#	@ O:Vcddr   c                    d}d}d}d}d}d}d}|j                  dd       rd}d}|s4t        j                  D ]!  }	|	j                         }	|	|vrd||	<   d||	<   # |j	                  d      dk(  r|d= n,|j	                  dd      dk7  rt        |d         |d<   | |d	<   |D 
cg c]  }
|
j                  d
      s|
dd  |
f c}
D ]  \  }}||   ||<   ||=  |j	                  d      t        j                  j                  k7  s,|j	                  d      t        j                  j                  k7  rd}|j	                  d      t        j                  j                  k7  sb|j	                  d      t        t        j                  j                        k7  s,|j	                  d      t        t        j                  j                        k7  s|j	                  d      t        t        j                  j                        k7  s|j	                  d      t        t        j                  j                         k7  s|j	                  d      t        j                  j"                  k7  sa|j	                  d      t        j                  j$                  k7  s5|j	                  d      t        t        j                  j&                        k7  rd}|j	                  d      t        t        j                  j(                        k7  s,|j	                  d      t        j                  j$                  k7  rd}|j	                  d      r|j	                  d      r|j	                  d      t        j                  j*                  k7  s|j	                  d      t        j                  j,                  k7  sX|j	                  d      t        j                  j.                  k7  s,|j	                  d      t        j                  j0                  k7  rd}|j	                  d      rJt3        |j5                               D ]  }|j                  d      s||=  |d   j7                  d      |d<   |j	                  d       rJt3        |j5                               D ]  }|j                  d!      s||=  |d    j7                  d      |d <   |j	                  d"      rJt3        |j5                               D ]  }|j                  d#      s||=  |d"   j7                  d      |d"<   |j                  d$d       s|rd}d}d}|j                  d%d       rd}t        j8                  t        j                  z   }|j;                         D ci c]  \  }}|j=                         |v s|| }}}|rd|d&<   t        j                  j?                  |       t        j                  jA                          |rOtB        jD                  d'k(  rtG        jH                          n'tB        jD                  d(k(  rtK        jH                          |r8tM        jN                          t        jP                  rtS        jT                          |r+tW        jX                          t[        j\                  dd)       |rt        j^                          |r<ta        t        j                  j.                  t        j                  j0                         |r2tc        jd                  tf        jh                  *      jk                          |r2tc        jd                  tl        jn                  *      jk                          d+d,d-S c c}
w c c}}w ).NF	first_runTr   rj   r  r  r   jwt_update_secretuse_   launch_startuplaunch_browsercheck_githubcheck_github_intervalrefresh_libraries_intervalrefresh_users_intervalpms_update_check_intervalmonitor_pms_updatespms_url_manualbackup_intervalr   enable_httpshttps_create_certhttps_domainhttps_ip
https_cert	https_keyr   zhsec-,r  zhscard-r  zhlcard-server_changedauth_changedr   r;   r=   )logstartupr  r   zSettings saved.r   )8r(  r   r  r  rw   r
   rv   rP   rR   LAUNCH_STARTUPLAUNCH_BROWSERCHECK_GITHUBrO   CHECK_GITHUB_INTERVALREFRESH_LIBRARIES_INTERVALREFRESH_USERS_INTERVALPMS_UPDATE_CHECK_INTERVALMONITOR_PMS_UPDATESPMS_URL_MANUALBACKUP_INTERVALr   HTTPS_DOMAINHTTPS_IP
HTTPS_CERT	HTTPS_KEYr  keysr)  r
  itemsupperprocess_kwargsr   r   PLATFORMr<   set_startupr>   r#   get_server_resourcesWS_CONNECTEDr)   	reconnectr*   restartr   connect_serverr   r/   r  r   r   rK  r"  r'   r  )r   ra   r  startup_changedr'  
reschedulehttps_changedrK  r  checked_configr  plain_config
use_configkall_settingsvs                   rf   configUpdatezWebInterface.configUpdateu  s    	
! ::k4(I!N"("9"9 /!/!5!5!7!/-.F>*-.F>*/ ::o&&0'zz/2."4*3F?4K*L'7@=F&'=C(\q||TZG[!AB%(\ 	#$L*#)*#5F< z"	#
 ::&'6==+G+GG

+,0L0LL"O ::n%)C)CC

23s6==;^;^7__

78C@h@h<ii

34FMM<`<`8aa

673v}}?f?f;gg

01V]]5V5VV

+,0L0LL

,-V]]5R5R1SSJ ::i C(=(=$>>

+,0L0LL!N ::n%&**5H*Izz.)V]]-G-GGJJz*fmm.D.DDJJ|,0H0HHJJ{+v}}/F/FF $ ::o&&++-( "<<(q	" '-_&=&C&CC&HF?# ::()&++-( "<<	*q	" *00B)C)I)I#)NF%& ::*+&++-( "<<	*q	" ,22F+G+M+Mc+RF'( ::&-!N M $ ::nd+ M)@)@@#)<<>O41aQWWY,5N!Q$OO+,F'($$V, 	 )+##%H,!!# '')""$$& **tTB '') %fmm&>&>@W@WX I$?$?@FFH E$7$78>>@#0ABB[ )]B Ps   #]:	].]	]c                     t        j                         }|j                  d      }|j                  sdt        j
                  _        y y )NT)return_responsei  )r#   r   get_plextv_resourcesokr   responser   )r   ra   r   rR  s       rf   check_pms_tokenzWebInterface.check_pms_token
  s<     --////E{{'*H$ r   c                 R    t        j                         }|j                  |      }|S )N)update_channel)r#   r   get_plex_downloads)r   rU  ra   r   	downloadss        rf   get_pms_downloadszWebInterface.get_pms_downloads  s(     --/..n.M	r   c                 0    t        j                  dddi|S )Nreturn_serverTrJ   )r#   r?  r   s     rf   r?  z!WebInterface.get_server_resources  s     **HHHHr   c                 B    t        j                         }|rdddS dddS )/ Creates a manual backup of the plexpy.db file r   zConfig backup successful.r   r   zConfig backup failed.)r   make_backupr%  s      rf   backup_configzWebInterface.backup_config!  s-     ##%'4OPP%2IJJr   c                     t        d      S )Nzconfiguration_table.htmlr`   r,  r   s     rf   get_configuration_tablez$WebInterface.get_configuration_table.  s     ,FGGr   c                     t        d      S )Nzscheduler_table.htmlr`  r,  r   s     rf   get_scheduler_tablez WebInterface.get_scheduler_table3  s     ,BCCr   c                     t        d|      S )Nzqueue_modal.html)r`   queuer,  )r   re  ra   s      rf   get_queue_modalzWebInterface.get_queue_modal8  s     ,>eLLr   c                    t        j                         }|j                         }t        j                         j                         }|t        j                  j                  t        j                  j                  t        j                  j                        t        j                  j                  t        j                  j                  t        j                  j                  dS )N)plexpasspms_platformpms_update_channelpms_update_distropms_update_distro_build)r#   r   get_plexpass_statusr&   r   get_server_update_channelr   PMS_PLATFORM_NAME_OVERRIDESrw   rP   rR   PMS_PLATFORMPMS_UPDATE_CHANNELPMS_UPDATE_DISTROPMS_UPDATE_DISTRO_BUILD)r   ra   r   rh  rU  s        rf   get_server_update_paramsz%WebInterface.get_server_update_params=  s     --/..0#..0JJL$ & B B F FMM..0J0J!L&,mm&F&F%+]]%D%D+1==+P+PR 	Rr   c                 B    t        j                         }|rdddS dddS )r\  r   zDatabase backup successful.r   r   zDatabase backup failed.)r   r]  r%  s      rf   	backup_dbzWebInterface.backup_dbM  s-     %%''4QRR%2KLLr   c                 2    t        j                  |      }|S )aF   Get a list of configured notifiers.

            ```
            Required parameters:
                None

            Optional parameters:
                notify_action (str):        The notification action to filter out

            Returns:
                json:
                    [{"id": 1,
                      "agent_id": 13,
                      "agent_name": "telegram",
                      "agent_label": "Telegram",
                      "friendly_name": "",
                      "active": 1
                      }
                     ]
            ```
        )r  )r"   get_notifiers)r   r  ra   r   s       rf   rx  zWebInterface.get_notifiersZ  s    4 ((}Er   c                 D    t        j                         }t        d|      S )Nznotifiers_table.html)r`   notifiers_list)r"   rx  rg   r%  s      rf   get_notifiers_tablez WebInterface.get_notifiers_tablew  s!     ((*,BSYZZr   c                 F    t        j                  |      }|rdddS dddS )a   Remove a notifier from the database.

            ```
            Required parameters:
                notifier_id (int):        The notifier to delete

            Optional parameters:
                None

            Returns:
                None
            ```
        r  r   zNotifier deleted successfully.r   r   zFailed to delete notifier.)r"   delete_notifierr   r  ra   r   s       rf   r~  zWebInterface.delete_notifier}  s/    $ **{C'4TUU%2NOOr   c                 4    t        j                  |d      }|S )a    Get the configuration for an existing notification agent.

            ```
            Required parameters:
                notifier_id (int):        The notifier config to retrieve

            Optional parameters:
                None

            Returns:
                json:
                    {"id": 1,
                     "agent_id": 13,
                     "agent_name": "telegram",
                     "agent_label": "Telegram",
                     "friendly_name": "",
                     "config": {"incl_poster": 0,
                                "html_support": 1,
                                "chat_id": "123456",
                                "bot_token": "13456789:fio9040NNo04jLEp-4S",
                                "incl_subject": 1,
                                "disable_web_preview": 0
                                },
                     "config_options": [{...}, ...]
                     "actions": {"on_play": 0,
                                 "on_stop": 0,
                                 ...
                                 },
                     "notify_text": {"on_play": {"subject": "...",
                                                 "body": "..."
                                                 }
                                     "on_stop": {"subject": "...",
                                                 "body": "..."
                                                 }
                                     ...
                                     }
                     }
            ```
        Tr  mask_passwords)r"   get_notifier_configr  s       rf   r  z WebInterface.get_notifier_config  s    X ..;W[\r   c           	          t        j                  |d      }t        j                  D cg c]  }|d   D ]  }|d   |d   |d   d  }}}t	        d||	      S c c}}w )
NTr  
parametersnametyper  r  r  r  znotifier_config.html)r`   notifierr  )r"   r  r   NOTIFICATION_PARAMETERSrg   )r   r  ra   r   categoryparamr  s          rf   get_notifier_config_modalz&WebInterface.get_notifier_config_modal  s     ..;W[\ !' > >HUaLbCH vfgWW
 
 ,BV`jkks    Ac                 J    t        j                  dd|i|}|rdd|dS dddS )	a   Add a new notification agent.

            ```
            Required parameters:
                agent_id (int):           The notification agent to add

            Optional parameters:
                None

            Returns:
                None
            ```
        agent_idr   zAdded notification agent.)r   r   r  r   z!Failed to add notification agent.r   rJ   )r"   add_notifier_configr   r  ra   r   s       rf   r  z WebInterface.add_notifier_config  s:    $ ..KKFK'4O`fgg%2UVVr   c                 J    t        j                  d||d|}|rdddS dddS )a   Configure an existing notification agent.

            ```
            Required parameters:
                notifier_id (int):        The notifier config to update
                agent_id (int):           The agent of the notifier

            Optional parameters:
                Pass all the config options for the agent with the agent prefix:
                    e.g. For Telegram: telegram_bot_token
                                       telegram_chat_id
                                       telegram_disable_web_preview
                                       telegram_html_support
                                       telegram_incl_poster
                                       telegram_incl_subject
                Notify actions (int):  0 or 1,
                    e.g. on_play, on_stop, etc.
                Notify text (str):
                    e.g. on_play_subject, on_play_body, etc.

            Returns:
                None
            ```
        )r  r  r   zSaved notification agent.r   r   z"Failed to save notification agent.rJ   )r"   set_notifier_config)r   r  r  ra   r   s        rf   r  z WebInterface.set_notifier_config  s;    : ..d;QYd]cd'4OPP%2VWWr   c           	      2   t        |      j                         rt        |      }g }t        fdt	        j
                         D        d      }|D ]7  }	t        j                  ||d|	i|d      \  }
}|j                  |	|
|d       9 t        d||      S )	Nc              3   8   K   | ]  }|d    k(  r|d     yw)r  media_typesNrJ   )r   ar  s     rf   r   z7WebInterface.get_notify_text_preview.<locals>.<genexpr>  s)      ; yM9 m, ;s   rJ   r  T)subjectbodyr  r  r  test)r  r  r  znotifier_text_preview.html)r`   textagent)
rO   isdigitr  rq   r"   available_notification_actionsr!   build_notify_textr&  rg   )r   r  r  r  r  r  ra   r  r  r  test_subject	test_bodys    `          rf   get_notify_text_previewz$WebInterface.get_notify_text_preview  s     x=  "8}H ;i6^6^6` ;<>@ & 	`J&:&L&LU\RV[hYegqXrV^RV'X#L) KKzlT]^_	` ,Ht[effr   c           	      ~    t         j                  D cg c]  }|d   D ]  }|d   |d   |d   d  }}}|S c c}}w )a#   Get the list of available notification parameters.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    {
                     }
            ```
        r  r  r  r  r  )r   r  )r   ra   r  r  r  s        rf   get_notifier_parametersz$WebInterface.get_notifier_parameters#  si    0 '-&D&D	; ##+L#9;
    %V}$V} %g ;  ;
 ; ;s    9c           	         dt         j                  j                  d<   |dk(  rdnd}|rzt        j                  |      }|r>t        j                  d||d   d	       t        j                  d||||d
d| dddS t        j                  d|d|d       dd|z  dS t        j                  d|z         dddS )a   Send a notification using Tautulli.

            ```
            Required parameters:
                notifier_id (int):      The ID number of the notification agent
                subject (str):          The subject of the message
                body (str):             The body of the message

            Optional parameters:
                None

            Returns:
                None
            ```
        max-age=0,no-cache,no-storeCache-Controlr  test r   r}  Sending agent_labelz notification.T)r  r  r  r  manual_triggerr   Notification queued.r   Unable to send z"notification, invalid notifier_id r  r   zInvalid notifier id %s.z7Unable to send %snotification, no notifier_id received.zNo notifier id received.rJ   )	r   rR  headersr"   r  r   rV  r!   add_notifier_each)r   r  r  r  r  ra   r  r  s           rf   send_notificationzWebInterface.send_notification@  s    & 6S!!/2'61wr 44MHT8MCZ[\$66 A;ER?F<@FJ	A
 :@A #,8NOOY]_jkl")6OR]6]^^LLRUYYZ%2LMMr   c                 n    t        j                         }|r
|d   }|r|S y t        j                  d       y )Nnotificationsz)Unable to retrieve browser notifications.)r"   get_browser_notificationsr   r   )r   ra   r   r  s       rf   r  z&WebInterface.get_browser_notificationsj  s;     446"?3M$$KKCDr   c                     dt         j                  j                  d<   t        j                         }|j                  |||      }|rdd|dS ddd	S )
Nr  r  )app_id
app_secretredirect_urir   z;Confirm Authorization. Check pop-up blocker if no response.)r   r  r   r   z%Failed to retrieve authorization url.r   r  )r   rR  r  r"   FACEBOOK_get_authorization)r   r  r  r  ra   facebook_notifierr   s          rf   facebook_authzWebInterface.facebook_authz  sh     6S!!/2%..022&>H@L 3 N '0mvyzz%.UVVr   c                     dt         j                  j                  d<   t        j                         }|j                  |      }|rdj                  |      S y)Nr  r  zFacebook authorization successful. Tautulli can send notification to Facebook. Your Facebook access token is:<pre>{0}</pre>You may close this page.zqFailed to request authorization from Facebook. Check the Tautulli logs for details.<br />You may close this page.)r   rR  r  r"   r  _get_credentialsr~   )r   codera   facebookaccess_tokens        rf   facebook_redirectzWebInterface.facebook_redirect  sW     6S!!/2%%'00699?9MN Gr   c                     t         j                  j                  dk(  rddiS t         j                  j                  r5t         j                  j                  }dt         j                  _        dd|dS dd	d
S )Ntempr   waitingr   r   zAuthorization successful.)r   r  r  r   z Failed to request authorization.r  )rP   rR   FACEBOOK_TOKEN)r   ra   r   s      rf   facebook_retrieve_tokenz$WebInterface.facebook_retrieve_token  sd     ==''61i((]]))MM00E+-FMM('0K]bcc%.PQQr   c                     dt         j                  j                  d<   ddlm} |j	                  |      \  }}|r*t        j                         }|j                  dd|       |S t        j                  |       |S )Nr  r  r   )registerapp
RegisteredzSuccess :-))r  r  subtitle)
r   rR  r  	osxnotifyr  r"   OSXnotifyr   r   )r   appra   r  r   r  
osx_notifys          rf   osxnotifyregisterzWebInterface.osxnotifyregister  sp     6S!!/26++C0"JlQWX 
 KK
r   c                 f    t        j                  d|i      j                         }|rdddS dddS )Nhookr   r   zTest Zapier webhook sent.r  r   z#Failed to send test Zapier webhook.)r"   ZAPIER
_test_hook)r   zapier_hookra   r   s       rf   zapier_test_hookzWebInterface.zapier_test_hook  s=     ""6;*?@KKM'0KLL%.STTr   c                 &   |D cg c]  }|j                  d      s|dd  |f c}D ]  \  }}||   ||<   ||=  t        j                  j                  |       t        j                  j	                          dt
        j                  _        y c c}w )Nr  r     )rv   rP   rR   r<  r   r   rR  r   )r   ra   r  rH  rI  s        rf   set_notification_configz$WebInterface.set_notification_config  s     >D(\q||TZG[!AB%(\ 	#$L*#)*#5F< z"	#
 	$$V, 	#&  )]s
   B	Bc                 D    t        j                         }t        d|      S )Nzmobile_devices_table.html)r`   devices_list)r    get_mobile_devicesrg   r%  s      rf   get_mobile_devices_tablez%WebInterface.get_mobile_devices_table  s!     ..0,GV\]]r   c                     t        j                  |      rt        j                  |d       dddS t        j                  |      }|du rt        j                  |d       dd|dS dd	dS )
NT)r  r   zDevice registration cancelled.r   r   zDevice registered successfully.)r   r   r   zDevice not registered.)r   r   r    set_temp_device_tokenget_temp_device_token)r   device_tokencancelra   r   s        rf   verify_mobile_devicez!WebInterface.verify_mobile_device  sp     V$,,\$G%2RSS11,?T>,,\$G'4U_eff%2JKKr   c                 H    t        j                  |      }t        d|      S )N)mobile_device_idzmobile_device_config.html)r`   device)r    get_mobile_device_configrg   r   r  ra   r   s       rf   get_mobile_device_config_modalz+WebInterface.get_mobile_device_config_modal  s$     44FVW,GPVWWr   c                 H    t        j                  dd|i|}|rdddS dddS )aq   Configure an existing notification agent.

            ```
            Required parameters:
                mobile_device_id (int):        The mobile device config to update

            Optional parameters:
                friendly_name (str):           A friendly name to identify the mobile device

            Returns:
                None
            ```
        r  r   zSaved mobile device.r   r   zFailed to save mobile device.rJ   )r    set_mobile_device_configr  s       rf   r  z%WebInterface.set_mobile_device_config  s9    $ 44aFVaZ`a'4JKK%2QRRr   c                 H    t        j                  ||      }|rdddS dddS )a   Remove a mobile device from the database.

            ```
            Required parameters:
                mobile_device_id (int):        The mobile device database id to delete, OR
                device_id (str):               The unique device identifier for the mobile device

            Optional parameters:
                None

            Returns:
                None
            ```
        )r  	device_idr   zDeleted mobile device.r   r   zFailed to delete device.)r    delete_mobile_device)r   r  r  ra   r   s        rf   r  z!WebInterface.delete_mobile_device  s6    & 00BR;DF'4LMM%2LMMr   zresponse.timeouti  c                    |sdddS |rt         j                  j                  |      }	t         j                  j                  t        j
                  j                  |	dz         }
t        j                  d|	|
       t        j                  ||
      }n|rt         j                  j                  t        j
                  j                  |j                  dz         }t        j                  d|j                  |       t        |d      5 }	 |j                  j                  d      }|sn|j                  |       0	 ddd       |sdd	dS |j!                         d
k(  rt#        j$                  |      }|dk(  rOt'        j(                  t"        j*                  ||t-        j.                  |      d      j1                          dddS |rt-        j2                  |       d|dS |j!                         dk(  rtt5        j$                  ||      }|dk(  r<t'        j(                  t4        j6                  |||d      j1                          dddS |rt-        j2                  |       d|dS |j!                         dk(  rtt9        j$                  ||      }|dk(  r<t'        j(                  t8        j:                  |||d      j1                          dddS |rt-        j2                  |       d|dS dddS # 1 sw Y   xY w)a   Import a Tautulli, PlexWatch, or Plexivity database into Tautulli.

            ```
            Required parameters:
                app (str):                      "tautulli" or "plexwatch" or "plexivity"
                database_file (file):           The database file to import (multipart/form-data)
                or
                database_path (str):            The full path to the database file to import
                method (str):                   For Tautulli only, "merge" or "overwrite"
                table_name (str):               For PlexWatch or Plexivity only, "processed" or "grouped"


            Optional parameters:
                backup (bool):                  For Tautulli only, true or false whether to backup
                                                the current database before importing
                import_ignore_interval (int):   For PlexWatch or Plexivity only, the minimum number
                                                of seconds for a stream to import

            Returns:
                json:
                    {"result": "success",
                     "message": "Database import has started. Check the logs to monitor any problems."
                     }
            ```
        r   zNo app specified for importr   z
.import.dbz;Received database file '%s' for import. Saving to cache: %swb    Nz No database specified for importtautullir   r   )r   methodbackup)r  ra   zDDatabase import has started. Check the logs to monitor any problems.	plexwatch)database_file
table_name)r  r  import_ignore_interval	plexivityzApp not recognized for import)rL   rM   basenamerN   rP   rR   	CACHE_DIRr   r   shutilcopyfiler   r   r  r  r   r  r   validate_databaser  r   import_tautulli_dbr   r   r"  delete_filer%   import_from_plexwatchr$   import_from_plexivity)r   r  r  database_pathr  r  r  r  ra   database_file_namedatabase_cache_pathr   r   db_check_msgs                 rf   import_databasezWebInterface.import_database  s   @ %2OPP!#!1!1-!@"$'',,v}}/F/FHZ]iHi"jKKU*,?A"OOM;NOMGGLL)@)@-BXBX[gBghMKKU%..?mT* "a(--2248DGGDM	  	" %2TUU99;*$#55}MLy(  (C(C5B393:3D3DV3L)NO PUuw"+#ik k !''6")lCCYY[K'+==MISULy(  (8(N(N:G7ACY)[\ ]b\a\c"+#ik k !''6")lCCYY[K'+==MISULy(  (8(N(N:G7ACY)[\ ]b\a\c"+#ik k !''6")lCC &2QRRo" "s   2K44K>c                    t         j                  rdddS |rt        j                  j	                  t
        j                  j                  |j                  dz         }t        j                  d|j                  |       t        |d      5 }	 |j                  j                  d      }|sn|j                  |       0	 ddd       |sdd	dS t        j                   |t#        j$                  |      
       dddS # 1 sw Y   ?xY w)a   Import a Tautulli config file.

            ```
            Required parameters:
                config_file (file):             The config file to import (multipart/form-data)
                or
                config_path (str):              The full path to the config file to import


            Optional parameters:
                backup (bool):                  true or false whether to backup
                                                the current config before importing

            Returns:
                json:
                    {"result": "success",
                     "message": "Config import has started. Check the logs to monitor any problems. "
                                "Tautulli will restart automatically."
                     }
            ```
        r   zTDatabase import is in progress. Please wait until it is finished to import a config.r   z.import.iniz;Received config file '%s' for import. Saving to cache '%s'.r  r  NzNo config specified for import)r   r  r   zgConfig import has started. Check the logs to monitor any problems. Tautulli will restart automatically.)r   r  rL   rM   rN   rP   rR   r  r   r   r   r   r  r  r   r   set_import_threadr   r   )r   config_fileconfig_pathr  ra   r   r   s          rf   import_configzWebInterface.import_config  s    4   %uw w '',,v}}'>'>@T@TWd@deKKKU#,,k;k4( "A&++006DGGDM	  	" %2RSS  G<M<Mf<UV#BC 	C" "s   
2C;;Dc                     |dk(  rt        ddd      S |dk(  rt        ddd      S |d	k(  rt        dd
d      S t        j                  d       y )Nr  zapp_import.htmlzImport Tautulli DatabaseTautulli)r`   r   r  r  zImport PlexWatch Database	PlexWatchr  zImport Plexivity Database	PlexivityzNo app specified for import.)rg   r   r   )r   r  ra   s      rf   import_database_toolz!WebInterface.import_database_tool  sb     *!0AIcisttK!0AIdjuvvK!0AIdjuvv23r   c                     t        dd      S )Nzconfig_import.htmlzImport Tautulli Configurationr+  r,  r   s     rf   import_config_toolzWebInterface.import_config_tool  s     ,@Hghhr   c                     |r$t        j                  |      j                  d      }|st        j                  }t        j                  ||      }|rd||dS dddS )NzUTF-8)rM   
filter_extr   )r   rM   r   r   zInvalid path.r   )base64	b64decodedecoderP   DATA_DIRr   browse_path)r   r  rM   r  r   s        rf   r  zWebInterface.browse_path  s[     ##C(//8D??D""D'tDD%/BBr   c                    t        j                  |      }|s|r|rt        j                         }	|	j	                         }
t        |      }|
D ]"  }|d   |k(  s	|d   |k(  s|d   |k(  s|d   } n |sn|rdnd}dj                  |||      }d}t        j                  |d	
      }|j                  |dd      }|r%|j                  d      d   }|j                  d      }d|i}|r0t        j                  |      r| j                  |||||      }|d   |d<   d|d<   t        j                  |      r|d   j                  ddd      dz   }dt        j                  j                   z  g}|rFd}t        j                  j"                  rdt%        j&                         i}ndt(        j*                  i}nd}d}t-        j.                  d|z         	 t1        j2                  |||      }|j5                          t-        j.                  d        d!|d<   |S |S t-        j>                  d#       |S # t0        j6                  t8        t:        f$ r(}t-        j<                  d"|z         d	|d<   Y d}~|S d}~ww xY w)$a   Get the PMS server identifier.

            ```
            Required parameters:
                hostname (str):     'localhost' or '192.160.0.10'
                port (int):         32400

            Optional parameters:
                ssl (int):          0 or 1
                remote (int):       0 or 1

            Returns:
                json:
                    {'identifier': '08u2phnlkdshf890bhdlksghnljsahgleikjfg9t'}
            ```
        ipportclientIdentifierhttpshttpz{scheme}://{hostname}:{port})schemehostnamer  z	/identityF)urls
ssl_verifyGETxml)urirequest_typeoutput_formatMediaContainerr   machineIdentifier
identifier)r   r   r   r  r   pms_urlr   Nwsrj   z/:/websockets/notificationszX-Plex-Token: %szsecure ca_certs	cert_reqsr   z!Testing %swebsocket connection...)headerssloptz%Websocket connection test successful.Tz$Websocket connection test failed: %sz&Unable to retrieve the PMS identifier.) r   r   r#   r   r   r.   r~   r   HTTPHandlermake_requestgetElementsByTagNamegetAttributer?  r  rP   rR   r   VERIFY_SSL_CERTcertifiwhere_ssl	CERT_NONEr   rV  	websocketcreate_connectionr  WebSocketExceptionr  r[   r   r   )r   r#  r  r-  sslmanualget_urltest_websocketra   r   serversr  serverr"  r   r(  request_handlerrequestxml_headr   ws_urlr2  securer3  test_wsre   s                             rf   get_server_idzWebInterface.get_server_id  s   , $ h4mmoG&&(G)J! 4LH,t
0JPVW]P^bfPf!'(:!;J $'V4;;6T\cg;h!".":":FK#M)663DIEJ 7 L &;;<LMaPH!)!6!67J!KJ
+  )22(<@;>BHBL	 3 N
 !'y 1u#t$$^4#E]2264CFccF06==3J3JJKF!*!==88&0'--/%BF&14>>%BF!#!%LL!Dv!MN-"+"="=fV\b"c%LM'+t
 M6MKK@AM &88'9M -%Ka%OP',tM	-s   AH0 0I6I11I6c                 V    t        j                  d      }|j                  dd       |S )a   Get the PMS server information.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    {"pms_identifier": "08u2phnlkdshf890bhdlksghnljsahgleikjfg9t",
                     "pms_ip": "10.10.10.1",
                     "pms_is_remote": 0,
                     "pms_name": "Winterfell-Server",
                     "pms_platform": "Windows",
                     "pms_plexpass": 1,
                     "pms_port": 32400,
                     "pms_ssl": 0,
                     "pms_url": "http://10.10.10.1:32400",
                     "pms_url_manual": 0,
                     "pms_version": "1.20.0.3133-fede5bdc7"
                    }
            ```
        T)return_infor   N)r#   r?  r(  )r   ra   rE  s      rf   get_server_infozWebInterface.get_server_info7  s'    < ,,>

>4(r   c                     t        j                         }|j                  |      }|r|S t        j                  d       |S )z Get a specified PMS server preference.

            ```
            Required parameters:
                pref (str):         Name of preference

            Returns:
                string:             Value of preference
            ```
        )prefz,Unable to retrieve data for get_server_pref.)r&   r   get_server_prefr   r   )r   rQ  ra   r   r   s        rf   rR  zWebInterface.get_server_prefY  s?     !++-,,$,7MKKFGMr   c                    d}|r3|t         j                  j                  k(  st        j                  |      rut
        j                  dk\  rt        j                  d      }nt        j                         }|s@|t         j                  j                  k(  r^t        j                  |      rut        j                  d       t        j                  j                  |       t        j                  |      rt        j                   |d       |S )Nr   )r  r      zNew API key generated.T)r}   )rP   rR   API_KEYr    get_mobile_device_by_tokenrn   version_infosecretstoken_urlsafegenerate_uuidr   r   _BLACKLIST_WORDSr}   r   r   r  )r   r  ra   apikeys       rf   generate_api_keyzWebInterface.generate_api_keyq  s     Ffmm&;&;;z?d?drx?y6) ..r2--/	 Ffmm&;&;;z?d?drx?y 	,-##F+V$,,V>r   c                    t        j                          t        j                  dddd}nit        j                  dk(  rddddt        j                  z  t        j
                  j                  t        j                  t        j                  d	t        j                  j                  d
t        j                  j                  dt        j                        d}nt        j                  dk(  rddddt        j                  t        j                  t        j                  t        j                  d	t        j                  j                  d
t        j                  j                  dt        j                  dt        j                        d}ndddd}t        j                  s t        j                   st        j"                  rt        j$                  |d<   |S )a   Check for Tautulli updates.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json
                    {"result": "success",
                     "update": true,
                     "message": "An update for Tautulli is available."
                    }
            ```
        Nr   z/You are running an unknown version of Tautulli.)r   updater   releaser   Tz,A new release (%s) of Tautulli is available.zhttps://github.com/r   z/releases/tag/)r   r_  r`  r   current_releaselatest_releaserelease_urlcommitFz)A newer version of Tautulli is available.z	/compare/z...)r   r_  r`  r   current_versionlatest_versioncommits_behindcompare_urlzTautulli is up to date.install_type)r(   check_updaterP   UPDATE_AVAILABLELATEST_RELEASEr   rX   r   anon_urlrR   GIT_USERGIT_REPOrW   LATEST_VERSIONCOMMITS_BEHINDDOCKERSNAPFROZENINSTALL_TYPE)r   ra   r_  s      rf   update_checkzWebInterface.update_check  s`   , 	!!#""* ' $!RF
 $$	1 ) $!%!ORXRgRg!g)/)>)>(.(=(=%,%5%5#]]33#]]33#224&5F $$0 ) $!&!L)/)?)?(.(=(=(.(=(=%,%5%5#]]33#]]33#33#22	4&5F  !* %!:F
 ==FKK6==%+%8%8F>"r   c           	         |}| j                         }|r|t        _        t        j                  j                  j                  d      r0dt        j                  j                  j                  d      z   dz   }nd}t        d||||||      S )Nr   zshutdown.html)r`   signalr   new_http_rootr   timerquote)random_arnold_quotesrP   SIGNALrR   rU   r  rg   )r   rx  r   rz  ra   r   r{  ry  s           rf   do_state_changezWebInterface.do_state_change  s    ))+"FM==""((-&--"9"9"?"?"DDsJMMOFRW,97RW_df 	fr   c                 (    | j                  ddd      S )NshutdownzShutting Down   r~  r   s     rf   r  zWebInterface.shutdown  s     ##JDDr   c                 (    | j                  ddd      S )NrB  
Restarting   r  r   s     rf   rB  zWebInterface.restart  s     ##I|R@@r   c                    t         j                  st         j                  r&t        j                  t         j
                  dz         dt         j                  _        t         j                  j                          | j                  ddd      S )Nr   rj   r_  Updatingx   )
rP   rr  rs  r   r   rU   rR   r   r   r~  r   s     rf   r_  zWebInterface.update  s`     ==FKK''(8(86(ABB /0+##Hj#>>r   c                 n   |t         j                  j                  k(  r>t        j                  d|z         t        j                  t         j                  dz         |t         j                  _        |t         j                  _        t         j                  j                          | j                  ddd      S )NzAlready on the %s branchr   checkoutzSwitching Git Branchesr  )rP   rR   
GIT_BRANCHr   r   r   r   rU   
GIT_REMOTEr   r~  )r   
git_remote
git_branchra   s       rf   checkout_git_branchz WebInterface.checkout_git_branch  s     111LL3j@A''(8(86(ABB $. #- ##J0H#NNr   c                 b    | j                  ddj                  t        j                        d      S )NresetzResetting to {}r  )r~  r~   r   rX   r   s     rf   reset_git_installzWebInterface.reset_git_install  s*     ##G->-E-Efnn-UWZ[[r   c                     t         j                  rt         j                  j                          | j                  d dd      S )NzImporting a Configr  )r   IMPORT_THREADr"  r~  r   s     rf   restart_import_configz"WebInterface.restart_import_config   s5       &&(##D*>CCr   c                 b   t        j                  |      }t        j                  |      }|r%t        j                  t        j
                  k(  rd}d}t        j                  |      r3dt        j                  _        t        j                  j                          t        j                  ||      S )NTFr   )latest_onlysince_prev_release)r   r   rP   PREV_RELEASEr   rX   rR   r   r   r(   read_changelog)r   r  r  update_shownra   s        rf   get_changelogzWebInterface.get_changelog  s     ''4$../AB&"5"5"GK!& \*23FMM/MM!**{Wijjr   c                    |r<t        |      j                         s#t        j                  t        j
                        d }t        j                  j                  t        j                  j                  d}|r't        j                         }	|	j                  |      }
ni }
|r't        j                         }|j                  ||      }|s,|dk(  r't        j                          }|j                  ||      }|rZt        j                          }|j#                  |      }|j%                  |       |j'                  |      }|j%                  |       |rG|d   r1t)        |d         s#t        j                  t        j
                        t+        d|d	|||

      S t-               r#t        j                  t        j
                        | j/                  |      S )N)r   pms_web_urlr  )r  rQ  r  )r  r!  )metadatarQ  z	info.htmlInfo)r`   r  r   r   r   	user_info)rO   r  r   r   rP   rU   rR   r   PMS_WEB_URLr'   r  rU  r&   r   get_metadata_detailsr   r  get_poster_infor_  get_lookup_infor5   rg   r3   update_metadata)r   r  r!  r   rQ  r  ra   r  r   r  r  r   r  poster_infolookup_infos                  rf   r   zWebInterface.info  s    c*o557''(8(899 %mm::!==44

 I!--g->II $//1K"77:Zd7eH Fi/&224L#88JUY8ZH&224L&666IKOOK(&666IKOOK(%.CH\DZ.[++F,<,<==!hV\)/)U U #$++F,<,<==++J77r   c                     t        j                         }|j                  ||      }|rt        d||d      S t	        j
                  d       t        dd d      S )Nr  zinfo_children_list.htmlzChildren List)r`   r   r  r   z.Unable to retrieve data for get_item_children.ri  )r&   r   r'  rg   r   r   r   r  r  ra   r   r   s         rf   r'  zWebInterface.get_item_childrenJ  sf     !++-..*Q[.\!0IPV-7P P KKHI!0IPT\kllr   c                     t        j                         }|j                  |      }|rt        d||      S t        dd |      S )Nr  zinfo_collection_list.htmlri  )r&   r   get_item_children_relatedrg   )r   r  r   ra   r   r   s         rf   r  z&WebInterface.get_item_children_relatedX  sL     !++-66*6M!0KRX`eff!0KRV^cddr   c                     |s|r)t        j                         }|j                  |||      }nd }|rt        d|d      S t	        j
                  d       t        dd d      S )Nr  r!  r  rg  rh  ri  z2Unable to retrieve data for item_watch_time_stats.)r   r  rj  rg   r   r   r   r  r!  r  ra   	item_datar   s          rf   item_watch_time_statsz"WebInterface.item_watch_time_statsd  si     #//1I33zPTak3lFF!0LSYanooKKLM!0LSW_lmmr   c                     |s|r)t        j                         }|j                  |||      }nd }|rt        d|d      S t	        j
                  d       t        dd d      S )Nr  rn  ro  ri  z,Unable to retrieve data for item_user_stats.)r   r  rp  rg   r   r   r  s          rf   item_user_statszWebInterface.item_user_statss  sh     #//1I--$[e-fFF!0IPV^lmmKKFG!0IPT\jkkr   c                     t        j                  |d      }|rDt        j                         }|j	                  ||||      }|r|S t        j                  d       |S t        j                  d       y)a    Get the watch time stats for the media item.

            ```
            Required parameters:
                rating_key (str):       Rating key of the item

            Optional parameters:
                media_type (str):       Media type of the item (only required for a collection)
                grouping (int):         0 or 1
                query_days (str):       Comma separated days, e.g. "1,7,30,0"

            Returns:
                json:
                    [
                        {
                            "query_days": 1,
                            "total_time": 0,
                            "total_plays": 0
                        },
                        {
                            "query_days": 7,
                            "total_time": 0,
                            "total_plays": 0
                        },
                        {
                            "query_days": 30,
                            "total_time": 0,
                            "total_plays": 0
                        },
                        {
                            "query_days": 0,
                            "total_time": 57776,
                            "total_plays": 13
                        }
                    ]
            ```
        Tr<  )r  r  r?  r  z6Unable to retrieve data for get_item_watch_time_stats.z;Item watch time stats requested but no rating_key received.N)r   r   r   r  rj  r   r   )r   r  r  r?  r  ra   r  r   s           rf   get_item_watch_time_statsz&WebInterface.get_item_watch_time_stats  sr    T $$X4@#//1I33z?I=E?I 4 KF TUKKUVr   c                     t        j                  |d      }|rCt        j                         }|j	                  |||      }|r|S t        j                  d       |S t        j                  d       y)a    Get the user stats for the media item.

            ```
            Required parameters:
                rating_key (str):       Rating key of the item

            Optional parameters:
                media_type (str):       Media type of the item (only required for a collection)
                grouping (int):         0 or 1

            Returns:
                json:
                    [
                        {
                            "friendly_name": "Jon Snow",
                            "user_id": 1601089,
                            "user_thumb": "",
                            "username": "jsnow@thewinteriscoming.com",
                            "total_plays": 6,
                            "total_time": 28743
                        },
                        {
                            "friendly_name": "DanyKhaleesi69",
                            "user_id": 8008135,
                            "user_thumb": "",
                            "username": "DanyKhaleesi69",
                            "total_plays": 5,
                            "total_time": 18583
                        }
                    ]
            ```
        Tr<  )r  r  r?  z0Unable to retrieve data for get_item_user_stats.z5Item user stats requested but no rating_key received.N)r   r   r   r  rp  r   r   )r   r  r  r?  ra   r  r   s          rf   get_item_user_statsz WebInterface.get_item_user_stats  so    J $$X4@#//1I--9C7? . AF NOKKOPr   get_children_metadatac                     t        j                         }|j                  ||      }|r|S t        j                  d       |S )a
   Get the metadata for the children of a media item.

            ```
            Required parameters:
                rating_key (str):       Rating key of the item
                media_type (str):       Media type of the item

            Optional parameters:
                None

            Returns:
                json:
                    {"children_count": 9,
                     "children_type": "season",
                     "title": "Game of Thrones",
                     "children_list": [
                         {...},
                         {"actors": [],
                          "added_at": "1403553078",
                          "art": "/library/metadata/1219/art/1562110346",
                          "audience_rating": "",
                          "audience_rating_image": "",
                          "banner": "",
                          "collections": [],
                          "content_rating": "",
                          "directors": [],
                          "duration": "",
                          "full_title": "Season 1"
                          "genres": [],
                          "grandparent_rating_key": "",
                          "grandparent_thumb": "",
                          "grandparent_title": "",
                          "guid": "com.plexapp.agents.thetvdb://121361/1?lang=en",
                          "labels": [],
                          "last_viewed_at": "1589992348",
                          "library_name": "TV Shows",
                          "media_index": "1",
                          "media_type": "season",
                          "original_title": "",
                          "originally_available_at": "",
                          "parent_media_index": "1",
                          "parent_rating_key": "1219",
                          "parent_thumb": "/library/metadata/1219/thumb/1562110346",
                          "parent_title": "Game of Thrones",
                          "rating": "",
                          "rating_image": "",
                          "rating_key": "1220",
                          "section_id": "2",
                          "sort_title": "",
                          "studio": "",
                          "summary": "",
                          "tagline": "",
                          "thumb": "/library/metadata/1220/thumb/1602176313",
                          "title": "Season 1",
                          "updated_at": "1602176313",
                          "user_rating": "",
                          "writers": [],
                          "year": ""
                          },
                          {...},
                          {...}
                         ]
                     }
            ```
        r  z:Unable to retrieve data for get_children_metadata_details.)r&   r   r'  r   r   )r   r  r  ra   r   r  s         rf   get_children_metadata_detailsz*WebInterface.get_children_metadata_details  sJ    L !++-00J<F 1 H OKKTUOr   notify_recently_addedc                 :   |rt        j                         }|j                  |      }|ddd}|d   dvr2|j                  |      }|d   D cg c]  }|d   s	|d    }	}|	|d	<   |r||d
<   t        j
                  j                  |       dddS dddS c c}w )a^   Send a recently added notification using Tautulli.

            ```
            Required parameters:
                rating_key (int):       The rating key for the media

            Optional parameters:
                notifier_id (int):      The ID number of the notification agent.
                                        The notification will send to all enabled notification agents if notifier id is not provided.

            Returns:
                json
                    {"result": "success",
                     "message": "Notification queued."
                    }
            ```
        r  
on_createdT)timeline_datar  r  r  )movieepisodetrackr  r  
child_keysr  r   r  r   r   zNotification failed.)r&   r   r  r'  rP   NOTIFY_QUEUEput)
r   r  r  ra   r   r  r   childrenr+  r  s
             rf   send_manual_on_createdz#WebInterface.send_manual_on_created@  s    , $//1K"77:7NH%-`deD%-JJ&88J8O?G?Xpe\abn\oeL1p
p%/\"&1]###D)'4JKK &2HII qs   
BBc                     d}|j                  d      s,dt        j                  j                  j                  dd      v rt	               rdnd}||d<    | j
                  di |S )z% See real_pms_image_proxy docs stringFr  zno-cacher  r   TrJ   )rw   r   rG  r  r3   real_pms_image_proxy)r   ra   r  s      rf   pms_image_proxyzWebInterface.pms_image_proxyi  sb     ::i J(2B2B2J2J2N2N`b2c$c24e$G#y(t((2622r   r  c                 	   dt         j                  j                  d<   t        |t              rM|j                  d      r<t        j                  j                  t        j                  d|      }t        |d      S |sy|sw|	t        j                  v rOt        j                  |	   }t        j                  j                  t        j                  d|      }t        |d      S t        j                  d       yt!        j"                  |j%                  d	            }|r8|s6|	r#|	j                  d
      rdj'                  |      }ndj'                  |      }|ro|j                  d      s^d}|j                  d      r|dz  }|dz
  }|t)        d|v       z  }|j+                  d      }dj                  |d|       }||   }||k7  r|}t-        j.                  ||||||||	|	      }|rd|iS dj'                  ||      }t        j                  j                  t        j0                  j2                  d      }t        j                  j                  ||      }t        j                  j5                  |      st        j6                  |       t!        j"                  |      }	 t        j0                  j8                  r|
sd|v rt:        t        |d      S # t:        $ r 	 t=        j>                         }d|j@                  _!        |jE                  |||||||||
	      }|r|d   r{|d   t         j                  j                  d<   t        j0                  j8                  r:d|vr6tG        |d      5 }|jI                  |d          ddd       n# 1 sw Y   nxY w|d   cY S tK        d      # tJ        $ r}t        j                  d|d |	d!       d"t         j                  j                  d<   |	t        j                  v rVt        j                  |	   }t        j                  j                  t        j                  d|      }t        |d      cY d}~cY S |	r% | jL                  d$|	d||||||d|
|d#|cY d}~cY S Y d}~Y yd}~ww xY ww xY w)%a   Gets an image from the PMS and saves it to the image cache directory.

            ```
            Required parameters:
                img (str):              /library/metadata/153037/thumb/1462175060
                or
                rating_key (str):       54321

            Optional parameters:
                width (str):            300
                height (str):           450
                opacity (str):          25
                background (str):       Hex color, e.g. 282828
                blur (str):             3
                img_format (str):       png
                fallback (str):         "poster", "cover", "art", "poster-live", "art-live", "art-live-full", "user"
                refresh (bool):         True or False whether to refresh the image cache
                return_hash (bool):     True or False to return the self-hosted image hash instead of the image

            Returns:
                None
            ```
        zmax-age=2592000r  zinterfaces/default/imagesr   	image/pngrM   content_typezNo image input received.Nreturn_hashartz/library/metadata/{}/artz/library/metadata/{}/thumbr!     z
/playlistsrj   r  	compositer   )	imgr  widthheightopacity
backgroundblurfallback	add_to_dbimg_hashz{}.{}imagesindexesT)	r  r  r  r  r  r  
img_formatclipr  r   zContent-typer  zPMS image request failedzFailed to get image z, falling back to r  r  )r  r  r  r  r  r  r  r  r  r  r  rJ   )'r   rR  r  r   rO   rv   rL   rM   rN   rP   rQ   r   r   DEFAULT_IMAGESr   r   r   r   rw   r~   r  r)  r!   set_hash_image_inforR   r  existsmkdirCACHE_IMAGESr	   r&   r   rF  _silent	get_imager   r   r[   r  )r   r  r  r  r  r  r  r  r  r  r  r  ra   fpfbir  partsrating_key_idx	img_splitimg_rating_keyr  c_dirffpr   r   r   re   s                              rf   r  z!WebInterface.real_pms_image_proxyu  s   6 6G!!/2c3CNN3N$Ofoovs;B2K@@:6000++H5WW\\&//63?!rDDKK23''

=(ABcH//6077
C299*Es~~f-E~~l+
"QYNS+,,E		#I((9Ve,-C&~6N^++
';;
%
!#
 ))^^Hj1V]]44h?ggll5"%ww~~e$HHUO  &*	M==--I<L3[AA $	M"M(3356:++3$..35:6<7>:D48:D487> / @ fQi@Fq	H%%--n=}}11is6J!#t_ /GGF1I./ / / "!9$#$>?? MSRZ[\=Z!!))/:v444 //9CfoovsCB%2KHH4444 M$U6 'JTV`!%wTM FLM M M3$	Mst   2K 
RBN'+N	 	N'	N	N'RN''	R0BQ>	R
RQ>0R1R8R>RRc                    |rKdt         j                  j                  d<   t        |      dk\  rk|d   dk(  rct        j
                  j                  t        t        j                        d      }	 t        t	        j
                  j                  |g| d      S |d   j                  d	      d   }|t        j                  v rOt        j                  |   }t        j
                  j                  t        j                  d
|      }t        |d      S t        j                   |      }|r%|j#                  |        | j$                  dddi|S y # t        $ r Y y w xY w)Nzmax-age=3600r  r  r   r  data/interfaces/default/r  r  r  r   )r  r  TrJ   )r   rR  r  r  rL   rM   rN   rO   rP   rQ   r   r	   r)  r   r  r!   get_hash_image_infor_  r  )r   r   ra   resource_dirr  r  r  img_infos           rf   imagezWebInterface.image  s#   9GH%%o64yA~$q'X"5!ww||C,@B\]%277<<+Lt+L[fgg Aw}}S)!,H6000++H5WW\\&//63?!rDD+??RHh'0t00HHHH!   s   /*E 	EEc                    t         j                  }t        j                  j	                  t
        j                  j                  |      }	 t
        j                  j                          t        j                  t
        j                  |       	 t        j                  |      }t         j                  D ]  }t        ||d        |j                          t%        ||      S #  Y YxY w#  dt        j                   _        Y yxY w)z+ Download the Tautulli configuration file. r     z)Error downloading config. Check the logs.r  )r   r  rL   rM   rN   rP   rR   r  r   r  r  CONFIG_FILEConfig_DO_NOT_DOWNLOAD_KEYSsetattrr   rR  r   r   )r   ra   r  config_copycfgr  s         rf   download_configzWebInterface.download_config
  s    
 ooggll6==#:#:KH	MM!OOF..<	?--,C33 &S"%&IIK
 k<<		?'*H$>s   
AC! AC( !C%(Dc                 F   t         j                  }t        j                  j	                  t
        j                  j                  |      }	 t        j                         }|j                  j                  d       t        j                  t
        j                  |       |j                  j                          t        j                  |      }	 |j                  d       t+        ||      S #  Y 8xY w#  t!        j"                  d       dt$        j&                  _        Y yxY w)z& Download the Tautulli database file. zbegin immediatez7UPDATE users SET user_token = NULL, server_token = NULLz1Failed to remove tokens from downloaded database.r  z+Error downloading database. Check the logs.r  )r   r  rL   rM   rN   rP   rR   r  MonitorDatabase
connectionexecuter  r  DB_FILErollbackactionr   r   r   rR  r   r   )r   ra   r  database_copydbs        rf   download_databasezWebInterface.download_database#  s    
 !))V]]%<%<mL	))+BMM!!"34OOFNNM:MM""$
 %%m4	AIIOP m-@@		ALLLM'*H$@s   
A-C+ C2 +C/2,D c                    |dk(  r!t         j                  }t         j                  }nF|dk(  r!t         j                  }t         j                  }n t         j
                  }t         j                   }	 |j                          t        t        j                  j                  t        j                  j                  |      |      S #  Y GxY w)aq   Download the Tautulli log file.

            ```
            Required parameters:
                None

            Optional parameters:
                logfile (str):          The name of the Tautulli log file,
                                        "tautulli", "tautulli_api", "plex_websocket"

            Returns:
                download
            ```
        r  r  r  )r   r  
logger_apir  logger_plex_websocketr  flushr   rL   rM   rN   rP   rR   r  )r   r  ra   r   r)  s        rf   download_logzWebInterface.download_log>  s    $ n$**H##C((55H..CH--C	IIK bggll6==+@+@(KRZ[[	s   .C Cc                    t         j                  j                  sy|j                  d      rd|d   j	                         z   }|xs ddz   }t
        j                  j                  t         j                  j                  |      }|rKt
        j                  j                  |      r,t
        j                  j                  |      }t        ||      S d|z  S )ak   Download the Plex log file.

            ```
            Required parameters:
                None

            Optional parameters:
                logfile (int):          The name of the Plex log file,
                                        e.g. "Plex Media Server", "Plex Media Scanner"

            Returns:
                download
            ```
        z(Plex log folder not set in the settings.r  r  zPlex Media Serverz.logr  zPlex log file '%s' not found.)rP   rR   PMS_LOGS_FOLDERrw   r  rL   rM   rN   isfiler  r   )r   r  ra   r  log_file_pathlog_file_names         rf   download_plex_logzWebInterface.download_plex_loga  s    $ }},,=::j!#fZ&8&C&C&EEG22f<V]]%B%BHM}5GG,,];M!-mDD2X==r   c                 &    | j                  d      S )z0 Delete and recreate the image cache directory. r  )folder)delete_cacher   s     rf   delete_image_cachezWebInterface.delete_image_cache  s        11r   c                    t         j                  j                  t        j                  j
                  |      }d}d|r|dz   ndz  }	 t        j                  |d       	 t        j                  |       t        j                  |       ||dS # t        $ r2}d}d|z  }t        j                  d	|d
|d       ||dcY d}~S d}~ww xY w# t        $ r2}d}d|z  }t        j                  d|d
|d       ||dcY d}~S d}~ww xY w)z* Delete and recreate the cache directory. r   zCleared the %scache.r  r   T)ignore_errorsr   zFailed to delete %s.zFailed to delete z: r  r   NzFailed to make %s.zFailed to create )rL   rM   rN   rP   rR   r  r  rmtreeOSErrorr   r\   makedirsr   )r   r  ra   	cache_dirr   r  re   s          rf   r  zWebInterface.delete_cache  s     GGLL!8!8&A	$BG	6MM)48	6KK	" 	C S11  	6F(94C9aHI$55		6  	6F&2C9aHI$55		6s<   B  C 	C
'C?C
C
	D'D=DDc                     t        j                  |      }t        j                         }|j	                  |||      }|rdd|j                         z  dS dddS )a   Delete the images uploaded to image hosting services.

            ```
            Required parameters:
                None

            Optional parameters:
                rating_key (int):       1234
                                        (Note: Must be the movie, show, season, artist, or album rating key)
                service (str):          'imgur' or 'cloudinary'
                delete_all (bool):      'true' to delete all images form the service

            Returns:
                json:
                    {"result": "success",
                     "message": "Deleted hosted images from Imgur."}
            ```
        r  service
delete_allr   zDeleted hosted images from %s.r   r   zFailed to delete hosted images.)r   r   r   r  delete_img_infor  r   r  r   r!  ra   r  r   s          rf   delete_hosted_imagesz!WebInterface.delete_hosted_images  sd    0 &&z2
"..0--Wak-l'4TW]WhWhWj4jkk%2STTr   c                 j    t        j                         }|j                  |||      }|rdddS dddS )av   Delete the 3rd party API lookup info.

            ```
            Required parameters:
                None

            Optional parameters:
                rating_key (int):       1234
                                        (Note: Must be the movie, show, artist, album, or track rating key)
                service (str):          'themoviedb' or 'tvmaze' or 'musicbrainz'
                delete_all (bool):      'true' to delete all images form the service

            Returns:
                json:
                    {"result": "success",
                     "message": "Deleted lookup info."}
            ```
        r  r   zDeleted lookup info.r   r   zFailed to delete lookup info.)r   r  delete_lookup_infor#  s          rf   r&  zWebInterface.delete_lookup_info  sF    0 #..000JPWdn0o'4JKK%2QRRr   c                     t        dd|      S )Nzsearch.htmlSearch)r`   r   r   r,  )r   r   ra   s      rf   r  zWebInterface.search  s     MQVWWr   r  c                     t        j                         }|j                  ||      }|r|S t        j                  d       |S )a&   Get search results from the PMS.

            ```
            Required parameters:
                query (str):        The query string to search for

            Optional parameters:
                limit (int):        The maximum number of items to return per media type

            Returns:
                json:
                    {"results_count": 69,
                     "results_list":
                        {"movie":
                            [{...},
                             {...},
                             ]
                         },
                        {"episode":
                            [{...},
                             {...},
                             ]
                         },
                        {...}
                     }
            ```
        r   ru  z+Unable to retrieve data for search_results.)r&   r   get_search_resultsr   r   )r   r   ru  ra   r   r   s         rf   search_resultszWebInterface.search_results  sB    @ !++-//e5/IMKKEFMr   c                 4   t        j                         }|j                  ||      }|r||d   |   i|d<   |dk(  r&|r$|d   d   D cg c]  }|d   |k(  r| c}|d   d<   |rt        d|d      S t	        j
                  d       t        dd d      S c c}w )	Nr*  results_listseasonmedia_indexzinfo_search_results_list.htmlzSearch Result Listri  z8Unable to retrieve data for get_search_results_children.)r&   r   r+  rg   r   r   )	r   r   ru  r  season_indexra   r   r   r/  s	            rf   get_search_results_childrenz(WebInterface.get_search_results_children  s     !++-//e5/I&0&2H2T%UF>"!lEKNE[\dEe 0Z639-3HL3X 17 0ZF>"8, !0OV\dxyyKKRS!0OVZbvww0Zs   Bc                     |}t        j                  |      }t        j                         }|j	                  |      }|r|r||d<   |rt        d||d      S t        j                  d       t        d||d      S )Nr  query_stringzupdate_metadata.htmlr  )r`   r   r_  r   z,Unable to retrieve data for update_metadata.)r   r   r   r  get_search_queryrg   r   r   )r   r  r   r_  ra   r4  r  s          rf   r  zWebInterface.update_metadata/  s     ""6*"..0---D\$0E.!!0Fe\bjpqqKKFG!0Fe\bjpqqr   c                 
   t        j                  |      }|rct        j                         }t	        j
                         }|j                  ||      }|j                  ||      }	|j                  ||	||      }

rd|
iS ddiS )aI   Update the metadata in the Tautulli database by matching rating keys.
            Also updates all parents or children of the media item if it is a show/season/episode
            or artist/album/track.

            ```
            Required parameters:
                old_rating_key (str):       12345
                new_rating_key (str):       54321
                media_type (str):           "movie", "show", "season", "episode", "artist", "album", "track"

            Optional parameters:
                None

            Returns:
                None
            ```
        r  )old_key_listnew_key_listr  single_updater   r  )r   r   r   r  r&   r   get_rating_keys_listr  )r   old_rating_keynew_rating_keyr  r9  ra   r  r   r7  r8  r   s              rf   update_metadata_detailsz$WebInterface.update_metadata_details@  s    ,  ))-8&224L$//1K'<<cm<nL&;;~bl;mL!11|?K=G@M 2 OF
 v&&122r   c                     t        j                         }|j                  ||      }|r|S t        j                  d       |S )a   Get a list of new rating keys for the PMS of all of the item's parent/children.

            ```
            Required parameters:
                rating_key (str):       '12345'
                media_type (str):       "movie", "show", "season", "episode", "artist", "album", "track"

            Optional parameters:
                None

            Returns:
                json:
                    {}
            ```
        r  z0Unable to retrieve data for get_new_rating_keys.)r&   r   r:  r   r   r  s         rf   get_new_rating_keysz WebInterface.get_new_rating_keysj  sB    * !++-11ZT^1_MKKJKMr   c                     t        j                         }|j                  ||      }|r|S t        j                  d       |S )a   Get a list of old rating keys from the Tautulli database for all of the item's parent/children.

            ```
            Required parameters:
                rating_key (str):       '12345'
                media_type (str):       "movie", "show", "season", "episode", "artist", "album", "track"

            Optional parameters:
                None

            Returns:
                json:
                    {}
            ```
        r  z0Unable to retrieve data for get_old_rating_keys.)r   r  r:  r   r   )r   r  r  ra   r  r   s         rf   get_old_rating_keysz WebInterface.get_old_rating_keys  sB    * #..022jU_2`MKKJKMr   c                     t        j                         }|j                  d      }|r|S t        j                  d       y)z Get all the current sessions. r  z2Unable to retrieve data for get_pms_sessions_json.F)r&   r   get_sessionsr   r   r   s       rf   get_pms_sessions_jsonz"WebInterface.get_pms_sessions_json  s:    
 !++-))&1MKKLMr   get_metadatac                     t        j                         }|j                  ||      }|r|S t        j                  d       |S )a(   Get the metadata for a media item.

            ```
            Required parameters:
                rating_key (str):       Rating key of the item, OR
                sync_id (str):          Sync ID of a synced item

            Optional parameters:
                None

            Returns:
                json:
                    {"actors": [
                        "Emilia Clarke",
                        "Lena Headey",
                        "Sophie Turner",
                        "Kit Harington",
                        "Peter Dinklage",
                        "Nikolaj Coster-Waldau",
                        "Maisie Williams",
                        "Iain Glen",
                        "John Bradley",
                        "Alfie Allen"
                     ],
                     "added_at": "1461572396",
                     "art": "/library/metadata/1219/art/1462175063",
                     "audience_rating": "7.4",
                     "audience_rating_image": "themoviedb://image.rating",
                     "banner": "/library/metadata/1219/banner/1462175063",
                     "collections": [],
                     "content_rating": "TV-MA",
                     "directors": [
                        "Jeremy Podeswa"
                     ],
                     "duration": "2998290",
                     "edition_title": "",
                     "full_title": "Game of Thrones - The Red Woman",
                     "genres": [
                        "Action/Adventure",
                        "Drama",
                        "Fantasy",
                        "Romance"
                     ],
                     "grandparent_guid": "plex://show/5d9c086c46115600200aa2fe",
                     "grandparent_guids": [
                         "imdb://tt0944947",
                         "tmdb://1399",
                         "tvdb://121361"
                     ],
                     "grandparent_rating_key": "1219",
                     "grandparent_slug": "game-of-thrones",
                     "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
                     "grandparent_title": "Game of Thrones",
                     "grandparent_year": "2011",
                     "guid": "plex://episode/5d9c1276e9d5a1001f4ff2fa",
                     "guids": [
                         "imdb://tt3658014",
                         "tmdb://1156503",
                         "tvdb://5469015"
                     ],
                     "labels": [],
                     "last_viewed_at": "1462165717",
                     "library_name": "TV Shows",
                     "live": 0,
                     "markers": [
                        {
                             "id": 908,
                             "type": "credits",
                             "start_time_offset": 2923863,
                             "end_time_offset": 2998197,
                             "first": true,
                             "final": true
                        },
                        {
                             "id": 908,
                             "type": "intro",
                             "start_time_offset": 1622,
                             "end_time_offset": 109135,
                             "first": null,
                             "final": null
                        }
                     ],
                     "media_index": "1",
                     "media_info": [
                         {
                             "aspect_ratio": "1.78",
                             "audio_channel_layout": "5.1",
                             "audio_channels": "6",
                             "audio_codec": "ac3",
                             "audio_profile": "",
                             "bitrate": "10617",
                             "channel_call_sign": "",
                             "channel_id": "",
                             "channel_identifier": "",
                             "channel_title": "",
                             "channel_thumb": "",
                             "channel_vcn": "",
                             "container": "mkv",
                             "height": "1078",
                             "id": "257925",
                             "optimized_version": 0,
                             "parts": [
                                 {
                                     "file": "/media/TV Shows/Game of Thrones/Season 06/Game of Thrones - S06E01 - The Red Woman.mkv",
                                     "file_size": "3979115377",
                                     "id": "274169",
                                     "indexes": 1,
                                     "streams": [
                                         {
                                             "id": "511663",
                                             "type": "1",
                                             "video_bit_depth": "8",
                                             "video_bitrate": "10233",
                                             "video_codec": "h264",
                                             "video_codec_level": "41",
                                             "video_color_primaries": "",
                                             "video_color_range": "tv",
                                             "video_color_space": "bt709",
                                             "video_color_trc": "",
                                             "video_dynamic_range": "SDR",
                                             "video_dovi_bl_present": 0,
                                             "video_dovi_el_present": 0,
                                             "video_dovi_level": 0,
                                             "video_dovi_present": 0,
                                             "video_dovi_profile": 0,
                                             "video_dovi_rpu_present": 0,
                                             "video_dovi_version": 0,
                                             "video_frame_rate": "23.976",
                                             "video_height": "1078",
                                             "video_language": "",
                                             "video_language_code": "",
                                             "video_profile": "high",
                                             "video_ref_frames": "4",
                                             "video_scan_type": "progressive",
                                             "video_width": "1920",
                                             "selected": 0
                                         },
                                         {
                                             "audio_bitrate": "384",
                                             "audio_bitrate_mode": "",
                                             "audio_channel_layout": "5.1(side)",
                                             "audio_channels": "6",
                                             "audio_codec": "ac3",
                                             "audio_language": "",
                                             "audio_language_code": "",
                                             "audio_profile": "",
                                             "audio_sample_rate": "48000",
                                             "id": "511664",
                                             "type": "2",
                                             "selected": 1
                                         },
                                         {
                                             "id": "511953",
                                             "subtitle_codec": "srt",
                                             "subtitle_container": "",
                                             "subtitle_forced": 0,
                                             "subtitle_format": "srt",
                                             "subtitle_language": "English",
                                             "subtitle_language_code": "eng",
                                             "subtitle_location": "external",
                                             "type": "3",
                                             "selected": 1
                                         }
                                     ]
                                 }
                             ],
                             "video_codec": "h264",
                             "video_framerate": "24p",
                             "video_full_resolution": "1080p",
                             "video_profile": "high",
                             "video_resolution": "1080",
                             "width": "1920"
                         }
                     ],
                     "media_type": "episode",
                     "original_title": "",
                     "originally_available_at": "2016-04-24",
                     "parent_guid": "plex://season/602e67e61d3358002c4120f7",
                     "parent_guids": [
                         "tvdb://651357"
                     ],
                     "parent_media_index": "6",
                     "parent_rating_key": "153036",
                     "parent_slug": "game-of-thrones",
                     "parent_thumb": "/library/metadata/153036/thumb/1462175062",
                     "parent_title": "Season 6",
                     "parent_year": "2016",
                     "rating": "",
                     "rating_image": "",
                     "rating_key": "153037",
                     "section_id": "2",
                     "slug": "game-of-thrones",
                     "sort_title": "Red Woman",
                     "studio": "Revolution Sun Studios",
                     "summary": "The fate of Jon Snow is revealed. Daenerys meets a strong man. Cersei sees her daughter once again.",
                     "tagline": "",
                     "thumb": "/library/metadata/153037/thumb/1462175060",
                     "title": "The Red Woman",
                     "updated_at": "1462175060",
                     "user_rating": "9.0",
                     "writers": [
                        "David Benioff",
                        "D. B. Weiss"
                     ],
                     "year": "2016"
                     }
            ```
        )r  r{  z1Unable to retrieve data for get_metadata_details.)r&   r   r  r   r   )r   r  r{  ra   r   r  s         rf   r  z!WebInterface.get_metadata_details  sJ    j !++-33z<C 4 E OKKKLOr   r  c                     d|v r|d   }t        j                         }|j                  ||||      }|r|S t        j                  d       |S )a   Get all items that where recently added to plex.

            ```
            Required parameters:
                count (str):        Number of items to return

            Optional parameters:
                start (str):        The item number to start at
                media_type (str):   The media type: movie, show, artist
                section_id (str):   The id of the Plex library section

            Returns:
                json:
                    {"recently_added":
                        [{"actors": [
                             "Kit Harington",
                             "Emilia Clarke",
                             "Isaac Hempstead-Wright",
                             "Maisie Williams",
                             "Liam Cunningham",
                          ],
                          "added_at": "1461572396",
                          "art": "/library/metadata/1219/art/1462175063",
                          "audience_rating": "8",
                          "audience_rating_image": "rottentomatoes://image.rating.upright",
                          "banner": "/library/metadata/1219/banner/1462175063",
                          "directors": [
                             "Jeremy Podeswa"
                          ],
                          "duration": "2998290",
                          "full_title": "Game of Thrones - The Red Woman",
                          "genres": [
                             "Adventure",
                             "Drama",
                             "Fantasy"
                          ],
                          "grandparent_rating_key": "1219",
                          "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
                          "grandparent_title": "Game of Thrones",
                          "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
                          "guids": [],
                          "labels": [],
                          "last_viewed_at": "1462165717",
                          "library_name": "TV Shows",
                          "media_index": "1",
                          "media_type": "episode",
                          "original_title": "",
                          "originally_available_at": "2016-04-24",
                          "parent_media_index": "6",
                          "parent_rating_key": "153036",
                          "parent_thumb": "/library/metadata/153036/thumb/1462175062",
                          "parent_title": "",
                          "rating": "7.8",
                          "rating_image": "rottentomatoes://image.rating.ripe",
                          "rating_key": "153037",
                          "section_id": "2",
                          "sort_title": "Red Woman",
                          "studio": "HBO",
                          "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
                          "tagline": "",
                          "thumb": "/library/metadata/153037/thumb/1462175060",
                          "title": "The Red Woman",
                          "user_rating": "9.0",
                          "updated_at": "1462175060",
                          "writers": [
                             "David Benioff",
                             "D. B. Weiss"
                          ],
                          "year": "2016"
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r  )r"  r  r  rQ  z7Unable to retrieve data for get_recently_added_details.)r&   r   r  r   r   )r   r"  r  r  rQ  ra   r   r   s           rf   r  z'WebInterface.get_recently_added_details  sY    d VJ ++-77e5]gt~7MKKQRMr   c                     t        j                         }|j                  d      }|r|S t        j                  d       y)z7 Get the friends list of the server owner for Plex.tv. r  z-Unable to retrieve data for get_friends_list.N)r#   r   get_plextv_friendsr   r   r   ra   r   r   s       rf   get_friends_listzWebInterface.get_friends_list  s4     --/++F3MKKGHr   c                     t        j                         }|j                  d      }|r|S t        j                  d       y)z: Get all details about a the server's owner from Plex.tv. r  z-Unable to retrieve data for get_user_details.N)r#   r   get_plextv_user_detailsr   r   rJ  s       rf   get_user_detailszWebInterface.get_user_details  s4     --/008MKKGHr   c                     t        j                         }|j                  d      }|r|S t        j                  d       y)z' Find all servers published on Plex.tv r  z,Unable to retrieve data for get_server_list.N)r#   r   get_plextv_server_listr   r   rJ  s       rf   r   zWebInterface.get_server_list  s4     --///7MKKFGr   c                     t        j                         }|j                  |d      }|r|S t        j                  d       y)z7 Get all items that are currently synced from the PMS. r  )r   r*  z+Unable to retrieve data for get_sync_lists.N)r#   r   get_plextv_sync_listsr   r   )r   r   ra   r   r   s        rf   get_sync_listszWebInterface.get_sync_lists  s:    
 --/..*TZ.[MKKEFr   c                     t        j                         }|j                  d      }|r|S t        j                  d       y )Nr  r*  z(Unable to retrieve data for get_servers.)r&   r   r   r   r   r   s       rf   get_serverszWebInterface.get_servers(  s:     !++-,,6,BMKKBCr   c                     t        j                         }|j                         }|r|S t        j                  d       |S )a   Get info about the PMS.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"port": "32400",
                      "host": "10.0.0.97",
                      "version": "0.9.15.2.1663-7efd046",
                      "name": "Winterfell-Server",
                      "machine_identifier": "ds48g4r354a8v9byrrtr697g3g79w"
                      }
                     ]
            ```
        z-Unable to retrieve data for get_servers_info.)r&   r   get_servers_infor   r   r   s       rf   rX  zWebInterface.get_servers_info4  s:    2 !++---/MKKGHMr   c                     t        j                         }|j                         }|r|S t        j                  d       |S )a   Get info about the local server.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"machine_identifier": "ds48g4r354a8v9byrrtr697g3g79w",
                      "version": "0.9.15.x.xxx-xxxxxxx"
                      }
                     ]
            ```
        z0Unable to retrieve data for get_server_identity.)r&   r   get_server_identityr   r   r   s       rf   rZ  z WebInterface.get_server_identityV  s:    , !++-002MKKJKMr   c                 `    t        j                         }|r|S t        j                  d       |S )z Get the name of the PMS.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                string:     "Winterfell-Server"
            ```
        z5Unable to retrieve data for get_server_friendly_name.)r&   get_server_friendly_namer   r   r%  s      rf   r\  z%WebInterface.get_server_friendly_nameu  s,    $ 446MKKOPMr   c                    	 t        j                  t        j                  j                        }|j                         }|rrt        fd|d   D        i       S rt        fd|d   D        i       S ddddddd}|d   D ]  }|d   dk(  r|d	xx   d
z  cc<   n#|d   dk(  r|dxx   d
z  cc<   n|dxx   d
z  cc<   |dxx   t        j                  |d         z  cc<   |d   dk(  r$|dxx   t        j                  |d         z  cc<   |dxx   t        j                  |d         z  cc<    |j                  |       |S t        j                  d       i S # t        $ r"}t        j                  d|z         Y d}~yd}~ww xY w)a7   Get the current activity on the PMS.

            ```
            Required parameters:
                None

            Optional parameters:
                session_key (int):    Session key for the session info to return, OR
                session_id (str):     Session ID for the session info to return

            Returns:
                json:
                    {"lan_bandwidth": 25318,
                     "sessions": [
                         {
                             "actors": [
                                 "Kit Harington",
                                 "Emilia Clarke",
                                 "Isaac Hempstead-Wright",
                                 "Maisie Williams",
                                 "Liam Cunningham",
                             ],
                             "added_at": "1461572396",
                             "allow_guest": 1,
                             "art": "/library/metadata/1219/art/1503306930",
                             "aspect_ratio": "1.78",
                             "audience_rating": "",
                             "audience_rating_image": "rottentomatoes://image.rating.upright",
                             "audio_bitrate": "384",
                             "audio_bitrate_mode": "",
                             "audio_channel_layout": "5.1(side)",
                             "audio_channels": "6",
                             "audio_codec": "ac3",
                             "audio_decision": "direct play",
                             "audio_language": "",
                             "audio_language_code": "",
                             "audio_profile": "",
                             "audio_sample_rate": "48000",
                             "bandwidth": "25318",
                             "banner": "/library/metadata/1219/banner/1503306930",
                             "bif_thumb": "/library/parts/274169/indexes/sd/1000",
                             "bitrate": "10617",
                             "channel_call_sign": "",
                             "channel_id": "",
                             "channel_identifier": "",
                             "channel_stream": 0,
                             "channel_title": "",
                             "channel_thumb": "",
                             "channel_vcn": "",
                             "children_count": "",
                             "collections": [],
                             "container": "mkv",
                             "container_decision": "direct play",
                             "content_rating": "TV-MA",
                             "deleted_user": 0,
                             "device": "Windows",
                             "directors": [
                                 "Jeremy Podeswa"
                             ],
                             "do_notify": 0,
                             "duration": "2998272",
                             "email": "Jon.Snow.1337@CastleBlack.com",
                             "file": "/media/TV Shows/Game of Thrones/Season 06/Game of Thrones - S06E01 - The Red Woman.mkv",
                             "file_size": "3979115377",
                             "friendly_name": "Jon Snow",
                             "full_title": "Game of Thrones - The Red Woman",
                             "genres": [
                                 "Adventure",
                                 "Drama",
                                 "Fantasy"
                             ],
                             "grandparent_guid": "com.plexapp.agents.thetvdb://121361?lang=en",
                             "grandparent_rating_key": "1219",
                             "grandparent_thumb": "/library/metadata/1219/thumb/1503306930",
                             "grandparent_title": "Game of Thrones",
                             "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
                             "height": "1078",
                             "id": "",
                             "indexes": 1,
                             "ip_address": "10.10.10.1",
                             "ip_address_public": "64.123.23.111",
                             "is_admin": 1,
                             "is_allow_sync": 1,
                             "is_home_user": 1,
                             "is_restricted": 0,
                             "keep_history": 1,
                             "labels": [],
                             "last_viewed_at": "1462165717",
                             "library_name": "TV Shows",
                             "live": 0,
                             "live_uuid": "",
                             "local": "1",
                             "location": "lan",
                             "machine_id": "lmd93nkn12k29j2lnm",
                             "media_index": "1",
                             "media_type": "episode",
                             "optimized_version": 0,
                             "optimized_version_profile": "",
                             "optimized_version_title": "",
                             "original_title": "",
                             "originally_available_at": "2016-04-24",
                             "parent_guid": "com.plexapp.agents.thetvdb://121361/6?lang=en",
                             "parent_media_index": "6",
                             "parent_rating_key": "153036",
                             "parent_thumb": "/library/metadata/153036/thumb/1503889210",
                             "parent_title": "Season 6",
                             "platform": "Plex Media Player",
                             "platform_name": "plex",
                             "platform_version": "2.4.1.787-54a020cd",
                             "player": "Castle-PC",
                             "product": "Plex Media Player",
                             "product_version": "3.35.2",
                             "profile": "Konvergo",
                             "progress_percent": "0",
                             "quality_profile": "Original",
                             "rating": "7.8",
                             "rating_image": "rottentomatoes://image.rating.ripe",
                             "rating_key": "153037",
                             "relay": 0,
                             "section_id": "2",
                             "secure": 1,
                             "session_id": "helf15l3rxgw01xxe0jf3l3d",
                             "session_key": "27",
                             "shared_libraries": [
                                 "10",
                                 "1",
                                 "4",
                                 "5",
                                 "15",
                                 "20",
                                 "2"
                             ],
                             "sort_title": "Red Woman",
                             "state": "playing",
                             "stream_aspect_ratio": "1.78",
                             "stream_audio_bitrate": "384",
                             "stream_audio_bitrate_mode": "",
                             "stream_audio_channel_layout": "5.1(side)",
                             "stream_audio_channel_layout_": "5.1(side)",
                             "stream_audio_channels": "6",
                             "stream_audio_codec": "ac3",
                             "stream_audio_decision": "direct play",
                             "stream_audio_language": "",
                             "stream_audio_language_code": "",
                             "stream_audio_sample_rate": "48000",
                             "stream_bitrate": "10617",
                             "stream_container": "mkv",
                             "stream_container_decision": "direct play",
                             "stream_duration": "2998272",
                             "stream_subtitle_codec": "",
                             "stream_subtitle_container": "",
                             "stream_subtitle_decision": "",
                             "stream_subtitle_forced": 0,
                             "stream_subtitle_format": "",
                             "stream_subtitle_language": "",
                             "stream_subtitle_language_code": "",
                             "stream_subtitle_location": "",
                             "stream_video_bit_depth": "8",
                             "stream_video_bitrate": "10233",
                             "stream_video_chroma_subsampling": "4:2:0",
                             "stream_video_codec": "h264",
                             "stream_video_codec_level": "41",
                             "stream_video_color_primaries": "",
                             "stream_video_color_range": "tv",
                             "stream_video_color_space": "bt709",
                             "stream_video_color_trc": "",
                             "stream_video_decision": "direct play",
                             "stream_video_dynamic_range": "SDR",
                             "stream_video_framerate": "24p",
                             "stream_video_full_resolution": "1080p",
                             "stream_video_height": "1078",
                             "stream_video_language": "",
                             "stream_video_language_code": "",
                             "stream_video_ref_frames": "4",
                             "stream_video_resolution": "1080",
                             "stream_video_scan_type": "progressive",
                             "stream_video_width": "1920",
                             "studio": "HBO",
                             "subtitle_codec": "",
                             "subtitle_container": "",
                             "subtitle_decision": "",
                             "subtitle_forced": 0,
                             "subtitle_format": "",
                             "subtitle_language": "",
                             "subtitle_language_code": "",
                             "subtitle_location": "",
                             "subtitles": 0,
                             "summary": "Jon Snow is dead. Daenerys meets a strong man. Cersei sees her daughter again.",
                             "synced_version": 0,
                             "synced_version_profile": "",
                             "tagline": "",
                             "throttled": "0",
                             "thumb": "/library/metadata/153037/thumb/1503889207",
                             "title": "The Red Woman",
                             "transcode_audio_channels": "",
                             "transcode_audio_codec": "",
                             "transcode_container": "",
                             "transcode_decision": "direct play",
                             "transcode_height": "",
                             "transcode_hw_decode": "",
                             "transcode_hw_decode_title": "",
                             "transcode_hw_decoding": 0,
                             "transcode_hw_encode": "",
                             "transcode_hw_encode_title": "",
                             "transcode_hw_encoding": 0,
                             "transcode_hw_full_pipeline": 0,
                             "transcode_hw_requested": 0,
                             "transcode_key": "",
                             "transcode_max_offset_available": 0,
                             "transcode_min_offset_available": 0,
                             "transcode_progress": 0,
                             "transcode_protocol": "",
                             "transcode_speed": "",
                             "transcode_throttled": 0,
                             "transcode_video_codec": "",
                             "transcode_width": "",
                             "type": "",
                             "updated_at": "1503889207",
                             "user": "LordCommanderSnow",
                             "user_id": 133788,
                             "user_rating": "",
                             "user_thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
                             "username": "LordCommanderSnow",
                             "video_bit_depth": "8",
                             "video_bitrate": "10233",
                             "video_chroma_subsampling": "4:2:0",
                             "video_codec": "h264",
                             "video_codec_level": "41",
                             "video_color_primaries": "",
                             "video_color_range": "tv",
                             "video_color_space": "bt709",
                             "video_color_trc": ",
                             "video_decision": "direct play",
                             "video_dynamic_range": "SDR",
                             "video_frame_rate": "23.976",
                             "video_framerate": "24p",
                             "video_full_resolution": "1080p",
                             "video_height": "1078",
                             "video_language": "",
                             "video_language_code": "",
                             "video_profile": "high",
                             "video_ref_frames": "4",
                             "video_resolution": "1080",
                             "video_scan_type": "progressive",
                             "video_width": "1920",
                             "view_offset": "1000",
                             "width": "1920",
                             "writers": [
                                 "David Benioff",
                                 "D. B. Weiss"
                             ],
                             "year": "2016"
                         }
                     ],
                     "stream_count": "1",
                     "stream_count_direct_play": 1,
                     "stream_count_direct_stream": 0,
                     "stream_count_transcode": 0,
                     "total_bandwidth": 25318,
                     "wan_bandwidth": 0
                     }
            ```
        r   c              3   4   K   | ]  }|d    k(  s|  ywr   rJ   r   s     rf   r   z,WebInterface.get_activity.<locals>.<genexpr>  s      bq!MBRVaBa br   r   c              3   4   K   | ]  }|d    k(  s|  yw)r   NrJ   )r   r   r   s     rf   r   z,WebInterface.get_activity.<locals>.<genexpr>  s      `q!L/U_B_ `r   r   )stream_count_direct_playstream_count_direct_streamstream_count_transcodetotal_bandwidthlan_bandwidthwan_bandwidthr   	transcoderb  rj   copyra  r`  rc  	bandwidthlocationlanrd  re  z)Unable to retrieve data for get_activity.z,Unable to retrieve data for get_activity: %sN)r&   r   rP   rR   r   r   rq   r   r  r_  r   r   r[   r\   )	r   r   r   ra   r   r   countsr   re   s	    ``      rf   get_activityzWebInterface.get_activity  s   X&	Q$//fmm6M6MNK 557F bF:,> bdfgg `F:,> `bdee678945-.+,+,.  
+ WA-.+=78A=8/0F:;<A<9:a?:,-1D1DQ{^1TT-}-/73F3Fq~3VV//73F3Fq~3VV/W f%GH	 	QKaOPP	Qs+   AE! E! 9CE! 
E! !	F*FFget_librariesc                     t        j                         }|j                         }|r|S t        j                  d       |S )a   Get a list of all libraries on your server.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"art": "/:/resources/show-fanart.jpg",
                      "child_count": "3745",
                      "count": "62",
                      "is_active": 1,
                      "parent_count": "240",
                      "section_id": "2",
                      "section_name": "TV Shows",
                      "section_type": "show",
                      "thumb": "/:/resources/show.png"
                      },
                     {...},
                     {...}
                     ]
            ```
        z4Unable to retrieve data for get_full_libraries_list.)r&   r   get_library_detailsr   r   r   s       rf   get_full_libraries_listz$WebInterface.get_full_libraries_list  s:    > !++-002MKKNOMr   	get_usersc                     t        j                         }|j                         }|r|S t        j                  d       |S )a   Get a list of all users that have access to your server.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"allow_guest": 1,
                      "do_notify": 1,
                      "email": "Jon.Snow.1337@CastleBlack.com",
                      "filter_all": "",
                      "filter_movies": "",
                      "filter_music": "",
                      "filter_photos": "",
                      "filter_tv": "",
                      "is_active": 1,
                      "is_admin": 0,
                      "is_allow_sync": 1,
                      "is_home_user": 1,
                      "is_restricted": 0,
                      "keep_history": 1,
                      "row_id": 1,
                      "shared_libraries": ["1", "2", "3"],
                      "thumb": "https://plex.tv/users/k10w42309cynaopq/avatar",
                      "user_id": "133788",
                      "username": "Jon Snow"
                      },
                     {...},
                     {...}
                     ]
            ```
        z0Unable to retrieve data for get_full_users_list.)r'   r  rq  r   r   )r   ra   r  r   s       rf   get_full_users_listz WebInterface.get_full_users_list  s8    R KKM	$$&MKKJKMr   c                     t        j                         }|j                  ||      }|r|S t        j                  d       |S )a<   Get a list of synced items on the PMS.

            ```
            Required parameters:
                None

            Optional parameters:
                machine_id (str):       The PMS identifier
                user_id (str):          The id of the Plex user

            Returns:
                json:
                    [{"audio_bitrate": "192",
                      "client_id": "95434se643fsf24f-com-plexapp-android",
                      "content_type": "video",
                      "device_name": "Tyrion's iPad",
                      "failure": "",
                      "item_complete_count": "1",
                      "item_count": "1",
                      "item_downloaded_count": "1",
                      "item_downloaded_percent_complete": 100,
                      "metadata_type": "movie",
                      "photo_quality": "74",
                      "platform": "iOS",
                      "rating_key": "154092",
                      "root_title": "Movies",
                      "state": "complete",
                      "sync_id": "11617019",
                      "sync_media_type": null,
                      "sync_title": "Deadpool",
                      "total_size": "560718134",
                      "user": "DrukenDwarfMan",
                      "user_id": "696969",
                      "username": "DrukenDwarfMan",
                      "video_bitrate": "4000"
                      "video_quality": "100"
                      },
                     {...},
                     {...}
                     ]
            ```
        rt  z-Unable to retrieve data for get_synced_items.)r#   r   rv  r   r   )r   r   r  ra   r   r   s         rf   rv  zWebInterface.get_synced_items  s@    ` --/))ZPW)XMKKGHMr   c                     t        j                         }|j                  d      }|r|S t        j                  d       y)z- Return details for currently syncing items. r  rU  z5Unable to retrieve data for get_sync_transcode_queue.N)r&   r   get_sync_transcode_queuer   r   r   s       rf   rv  z%WebInterface.get_sync_transcode_queueV  s:    
 !++-55F5KMKKOPr   c                     |dv rd}n|dv rd}t        j                  |d      }t        j                         }|j	                  |||||||||	|

      }|r|S t        j                  d       |S )	a   Get the homepage watch statistics.

            ```
            Required parameters:
                None

            Optional parameters:
                grouping (int):         0 or 1
                time_range (int):       The time range to calculate statistics, 30
                stats_type (str):       'plays' or 'duration'
                stats_start (int)       The row number of the stat item to start at, 0
                stats_count (int):      The number of stat items to return, 5
                stat_id (str):          A single stat to return, 'top_movies', 'popular_movies',
                                        'top_tv', 'popular_tv', 'top_music', 'popular_music', 'top_libraries',
                                        'top_users', 'top_platforms', 'last_watched', 'most_concurrent'
                section_id (int):       The id of the Plex library section
                user_id (int):          The id of the Plex user
                before (str):           Stats before and including the date, "YYYY-MM-DD"
                after (str):            Stats after and including the date, "YYYY-MM-DD"

            Returns:
                json:
                    [{"stat_id": "top_movies",
                      "stat_type": "total_plays",
                      "rows": [{...}]
                      },
                     {"stat_id": "popular_movies",
                      "rows": [{...}]
                      },
                     {"stat_id": "top_tv",
                      "stat_type": "total_plays",
                      "rows":
                        [{"content_rating": "TV-MA",
                          "friendly_name": "",
                          "grandparent_thumb": "/library/metadata/1219/thumb/1462175063",
                          "guid": "com.plexapp.agents.thetvdb://121361/6/1?lang=en",
                          "labels": [],
                          "last_play": 1462380698,
                          "live": 0,
                          "media_type": "episode",
                          "platform": "",
                          "rating_key": 1219,
                          "row_id": 1116,
                          "section_id": 2,
                          "thumb": "",
                          "title": "Game of Thrones",
                          "total_duration": 213302,
                          "total_plays": 69,
                          "user": "",
                          "users_watched": ""
                          },
                         {...},
                         {...}
                         ]
                      },
                     {"stat_id": "popular_tv",
                      "rows": [{...}]
                      },
                     {"stat_id": "top_music",
                      "stat_type": "total_plays",
                      "rows": [{...}]
                      },
                     {"stat_id": "popular_music",
                      "rows": [{...}]
                      },
                     {"stat_id": "last_watched",
                      "rows": [{...}]
                      },
                     {"stat_id": "top_libraries",
                      "stat_type": "total_plays",
                      "rows": [{...}]
                      },
                     {"stat_id": "top_users",
                      "stat_type": "total_plays",
                      "rows": [{...}]
                      },
                     {"stat_id": "top_platforms",
                      "stat_type": "total_plays",
                      "rows": [{...}]
                      },
                     {"stat_id": "most_concurrent",
                      "rows": [{...}]
                      }
                     ]
            ```
        )r   0r9  )rj   r  r;  Tr<  )
r?  r  r  stats_startr  stat_idrQ  r  r  r  z+Unable to retrieve data for get_home_stats.)r   r   r   r  r  r   r   )r   r?  r  r  ry  r  rz  rQ  r  r  r  ra   r  r   s                 rf   r  zWebInterface.get_home_statsc  s    | ! J8##J$$X4@"..0,,h8B8B9D9D5<8B5<4:38 - 	: MKKEFMr   arnoldc                 4    ddl }g d}|j                  |      S )z Get to the chopper! r   N)#z^To crush your enemies, see them driven before you, and to hear the lamentation of their women!z#Your clothes, give them to me, now!zDo it!zIf it bleeds, we can kill it.zSee you at the party Richter!zLet off some steam, Bennett.zI'll be back.zGet to the chopper!zHasta La Vista, Baby!zIt's not a tumor!zDillon, you son of a bitch!zBenny!! Screw you!!z5Stop whining! You kids are soft. You lack discipline.zNice night for a walk.zStick around!z4I need your clothes, your boots and your motorcycle.z'No, it's not a tumor. It's not a tumor!zI LIED!zAre you Sarah Connor?zI'm a cop you idiot!z!Come with me if you want to live.z&Who is your daddy and what does he do?z'Oh, cookies! I can't wait to toss them.z0Make it quick because my horse is getting tired.z'What killed the dinosaurs? The Ice Age!z!That's for sleeping with my wife!z1Remember when I said I'd kill you last... I lied!z2You want to be a farmer? Here's a couple of acres.z,Now, this is the plan. Get your ass to Mars.z9I just had a terrible thought... What if this is a dream?z4Well, listen to this one: Rubber baby buggy bumpers!z!Take your toy back to the carpet!z,My name is John Kimble... And I love my car.z!I eat Green Berets for breakfast.zPut that cookie down! NOW!)randomchoice)r   ra   r}  
quote_lists       rf   r|  z!WebInterface.random_arnold_quotes  s     
 	#
J }}Z((r   c                     |r!d|d   v r t               j                  d	i |S dt        j                  j                  d<   t        j                  t               j                  dd            j                  d      S )
Nv2r   application/json;charset=UTF-8Content-Typer   z Please use the /api/v2 endpoint.)result_typer  r  rJ   )	r+   _api_runr   rR  r  r  r  _api_respondsencode)r   r   ra   s      rf   apizWebInterface.api	  st    DDGO"46??,V,,8XH%%n5::df22w7Y 3 [ \\b\bcj\klr   c                 *    t        j                         S )aX   Get info about the Tautulli server.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    {"tautulli_install_type": "git",
                     "tautulli_version": "v2.8.1",
                     "tautulli_branch": "master",
                     "tautulli_commit": "2410eb33805aaac4bd1c5dad0f71e4f15afaf742",
                     "tautulli_platform": "Windows",
                     "tautulli_platform_release": "10",
                     "tautulli_platform_version": "10.0.19043",
                     "tautulli_platform_linux_distro": "",
                     "tautulli_platform_device_name": "Winterfell-Server",
                     "tautulli_python_version": "3.10.0"
                     }
            ```
        )rP   get_tautulli_infor   s     rf   r  zWebInterface.get_tautulli_info  s    : ''))r   c                 N    t        j                         }|j                         }|S )aM   Check for updates to the Plex Media Server.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    {"update_available": true,
                     "platform": "Windows",
                     "release_date": "1473721409",
                     "version": "1.1.4.2757-24ffd60",
                     "requirements": "...",
                     "extra_info": "...",
                     "changelog_added": "...",
                     "changelog_fixed": "...",
                     "label": "Download",
                     "distro": "english",
                     "distro_build": "windows-i386",
                     "download_url": "https://downloads.plex.tv/...",
                     }
            ```
        )r#   r   get_plex_updaterJ  s       rf   get_pms_updatezWebInterface.get_pms_update1  s"    > --/((*r   c                     d}|sd}nt        j                  |      sd|z  }|rd|dS t        j                         }|j	                  |      }|rd|dS dd|z  dS )	a   Get the geolocation info for an IP address.

            ```
            Required parameters:
                ip_address

            Optional parameters:
                None

            Returns:
                json:
                    {"city": "Mountain View",
                     "code": "US",
                     "continent": "NA",
                     "country": "United States",
                     "latitude": 37.386,
                     "longitude": -122.0838,
                     "postal_code": "94035",
                     "region": "California",
                     "timezone": "America/Los_Angeles",
                     "accuracy": null
                     }
            ```
        r   zNo IP address provided.zInvalid IP address provided: %sr   r   r   )r   r   z+Failed to lookup GeoIP info for address: %s)r   r8  r#   r   get_geoip_lookup)r   r  ra   r   r   geo_infos         rf   r  zWebInterface.get_geoip_lookupT  sr    : /G$$Z07*DG%'::--/++J7'::!.[^h.hiir   c                 0    t        j                  |      }|S )a
   Get the connection info for an IP address.

            ```
            Required parameters:
                ip_address

            Optional parameters:
                None

            Returns:
                json:
                    {"host": "google-public-dns-a.google.com",
                     "nets": [{"description": "Google Inc.",
                               "address": "1600 Amphitheatre Parkway",
                               "city": "Mountain View",
                               "state": "CA",
                               "postal_code": "94043",
                               "country": "United States",
                               ...
                               },
                               {...}
                              ]
                json:
                    {"host": "Not available",
                     "nets": [],
                     "error": "IPv4 address 127.0.0.1 is already defined as Loopback via RFC 1122, Section 3.2.1.3."
                     }
            ```
        )r   whois_lookup)r   r  ra   
whois_infos       rf   get_whois_lookupzWebInterface.get_whois_lookup  s    D ))*5
r   c                 *    t        j                         S r   )r   get_plexpy_urlr   s     rf   r  zWebInterface.get_plexpy_url  s     %%''r   c                 .    t        j                         }|S )aA   Get a list of configured newsletters.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    [{"id": 1,
                      "agent_id": 0,
                      "agent_name": "recently_added",
                      "agent_label": "Recently Added",
                      "friendly_name": "",
                      "cron": "0 0 * * 1",
                      "active": 1
                      }
                     ]
            ```
        )r   get_newslettersr%  s      rf   r  zWebInterface.get_newsletters  s    6 ,,.r   c                 D    t        j                         }t        d|      S )Nznewsletters_table.html)r`   newsletters_list)r   r  rg   r%  s      rf   get_newsletters_tablez"WebInterface.get_newsletters_table  s!     ,,.,DW]^^r   c                 F    t        j                  |      }|rdddS dddS )a   Remove a newsletter from the database.

            ```
            Required parameters:
                newsletter_id (int):        The newsletter to delete

            Optional parameters:
                None

            Returns:
                None
            ```
        r  r   z Newsletter deleted successfully.r   r   zFailed to delete newsletter.)r   delete_newsletterr   r  ra   r   s       rf   r  zWebInterface.delete_newsletter  s/    $ ..]K'4VWW%2PQQr   c                 4    t        j                  |d      }|S )a   Get the configuration for an existing notification agent.

            ```
            Required parameters:
                newsletter_id (int):        The newsletter config to retrieve

            Optional parameters:
                None

            Returns:
                json:
                    {"id": 1,
                     "agent_id": 0,
                     "agent_name": "recently_added",
                     "agent_label": "Recently Added",
                     "friendly_name": "",
                     "id_name": "",
                     "cron": "0 0 * * 1",
                     "active": 1,
                     "subject": "Recently Added to {server_name}! ({end_date})",
                     "body": "View the newsletter here: {newsletter_url}",
                     "message": "",
                     "config": {"custom_cron": 0,
                                "filename": "newsletter_{newsletter_uuid}.html",
                                "formatted": 1,
                                "incl_libraries": ["1", "2"],
                                "notifier_id": 1,
                                "save_only": 0,
                                "time_frame": 7,
                                "time_frame_units": "days"
                                },
                     "email_config": {...},
                     "config_options": [{...}, ...],
                     "email_config_options": [{...}, ...]
                     }
            ```
        Tr  r  )r   get_newsletter_configr  s       rf   r  z"WebInterface.get_newsletter_config  s    T 22_cdr   c                 J    t        j                  |d      }t        d|      S )NTr  znewsletter_config.html)r`   
newsletter)r   r  rg   r  s       rf   get_newsletter_config_modalz(WebInterface.get_newsletter_config_modal  s&     22_cd,DQWXXr   c                 J    t        j                  dd|i|}|rdd|dS dddS )	a   Add a new notification agent.

            ```
            Required parameters:
                agent_id (int):           The newsletter type to add

            Optional parameters:
                None

            Returns:
                None
            ```
        r  r   zAdded newsletter.)r   r   r  r   zFailed to add newsletter.r   rJ   )r   add_newsletter_configr  s       rf   r  z"WebInterface.add_newsletter_config  s:    $ 22OHOO'4GZ`aa%2MNNr   c                 J    t        j                  d||d|}|rdddS dddS )a   Configure an existing newsletter agent.

            ```
            Required parameters:
                newsletter_id (int):    The newsletter config to update
                agent_id (int):         The newsletter type of the newsletter

            Optional parameters:
                Pass all the config options for the agent with the 'newsletter_config_' and 'newsletter_email_' prefix.

            Returns:
                None
            ```
        )r  r  r   zSaved newsletter.r   r   zFailed to save newsletter.rJ   )r   set_newsletter_config)r   r  r  ra   r   s        rf   r  z"WebInterface.set_newsletter_config3  sC    & 22 =<D=5;= '4GHH%2NOOr   c           	         dt         j                  j                  d<   |dk(  rdnd}|rzt        j                  |      }|r>t        j                  d||d   d	       t        j                  d|||||d
| dddS t        j                  d|d|d       dd|z  dS t        j                  d|z         dddS )a   Send a newsletter using Tautulli.

            ```
            Required parameters:
                newsletter_id (int):      The ID number of the newsletter

            Optional parameters:
                None

            Returns:
                None
            ```
        r  r  r  r  r   r  r  r  z newsletter.)r  r  r  r  r   r   zNewsletter queued.r   r  z"newsletter, invalid newsletter_id r  r   zInvalid newsletter id %s.z9Unable to send %snotification, no newsletter_id received.zNo newsletter id received.rJ   )	r   rR  r  r   r  r   rV  r   add_newsletter_each)	r   r  r  r  r   r  ra   r  r  s	            rf   send_newsletterzWebInterface.send_newsletterO  s    " 6S!!/2'61wr$::WJ4MAZ[\"66 B]ER?F<@?F	B
 ;AB #,8LMMY]_lmn")6QTa6abbLLTW[[\%2NOOr   c                 @   t         j                  j                  d   }t        j                  j
                  dk(  r'|j                  dd      }t        j                  |      t        j                  j
                  dk(  rt        j                  j                  rut        |      dk\  r|d   dk(  r | j                  |i |S |j                  dd       t        j                  j                  k(  r | j                  |i |S t        d	d
|      S  | j                  |i |S )NREQUEST_URIr  z/newsletterz/newsletter_authrj   r   r  r  znewsletter_auth.htmlzNewsletter Login)r`   r   r(  )r   rG  wsgi_environrP   rR   NEWSLETTER_AUTHr  r   NEWSLETTER_PASSWORDr  newsletter_authr(  rg   )r   r   ra   request_urir  s        rf   r  zWebInterface.newsletterw  s    &&33MB==((A-&..}>PQL''55]]**a/FMM4U4U4yA~$q'W"4+t++T<V<<E4(FMM,M,MM+t++T<V<<%4J,>*57 7
 (4''888r   c                    |rt        |      dk\  r|d   dk(  r|d   dk(  rft        j                  j                  t	        t
        j                        d      }	 t        t        j                  j                  |g|dd   d      S | j                  |d         S t        |      dk\  r|d   d	k(  r|d   }d }nd }|d   }t        j                  ||
      }|S y # t        $ r Y y w xY w)Nr  r   r  rj   r  r  r  r  r4  )newsletter_uuidnewsletter_id_name)r  rL   rM   rN   rO   rP   rQ   r   r	   r  r   get_newsletter)r   r   ra   r  r  r  r  s          rf   r  zWebInterface.newsletter_auth  s     4yA~$q'W"47h&#%77<<FOO0DF`#aL)rww||L/T4PQPR8/Tcnoo zz$q'**4yA~$q'T/%)!W""&%)""&q'+::?N`bJ+  $ s   -C 	C&%C&c                 (    d|d<   t        dd|      S )Ntruepreviewznewsletter_preview.html
Newsletter)r`   r   ra   r,  r   s     rf   newsletter_previewzWebInterface.newsletter_preview  s$     #y,E$0%+- 	-r   c                    |r|dk7  rt        j                  |      }|rt        j                  ||d   |d   |d   |||d   |d   |d   		      }t        j                  |      }t        j                  |      }|rQd
t
        j                  j                  d<   t        j                  |j                  |            j                  d      S |j                  |      S t        j                  d|z         yt        j                  d       y)NNoner  id_namer  r   r  r  r   )	r  r  r  r   r  r  r  r  r   r  r  )r  r  z7Failed to retrieve newsletter: Invalid newsletter_id %sz>Failed to retrieve newsletter: invalid newsletter_id parameterz?Failed to retrieve newsletter: Missing newsletter_id parameter.z>Failed to retrieve newsletter: missing newsletter_id parameter)r   r  get_agent_classr   r   r   rR  r  r  r  raw_datar  generate_newsletterr   r   )	r   r  r  r  r  rawra   r  newsletter_agents	            rf   real_newsletterzWebInterface.real_newsletter  s    ]f4$::WJ#.#>#>]R\]fRgHRS]H^FPQYFZJTHPGQR[G\DNvDVGQR[G\$^  "++G4'',@`H%%--n=::&6&?&?&?&PQXXY`aa';;G;LLLLRUbbcSVWOr   c                     t        dd      S )Nzsupport.htmlSupportr+  r,  r   s     rf   supportzWebInterface.support  s     N)LLr   c                    dt         j                  j                  d<   ddd}|s|rt         j                  j                  dk(  s7t
        j                  r'g t         j                  j                  d<   t                d|d	d
 xs |j                  d      v r>t        j                         }|j                  |       |d   dk(  rd|d<   |S d|d<   d|d<   |S )af   Get the current status of Tautulli.

            ```
            Required parameters:
                None

            Optional parameters:
                check (str):        database

            Returns:
                json:
                    {"result": "success",
                     "message": "Ok",
                     }
            ```
        r  r  r   Okr   z/api/v2zauth.requirer   Nrj   checkintegrity_checkrQ  zDatabase okr   r   r   zDatabase not ok)r   rR  r  rG  	path_inforP   AUTH_ENABLEDr   r9   rw   r   r  r_  )r   r   ra   r   r   s        rf   r   zWebInterface.status  s    ( 6S!!/2%$76##--:v?R?R:<  ''7d2Ah=&**W*=>!113f%+,4(5F9%
  (/F8$(9F9%r   c                 f    dt         j                  j                  d<   dt        j                  d}|S )ap   Get the current status of Tautulli's connection to the Plex server.

            ```
            Required parameters:
                None

            Optional parameters:
                None

            Returns:
                json:
                    {"result": "success",
                     "connected": true,
                     }
            ```
        r  r  r   )r   	connected)r   rR  r  rP   PLEX_SERVER_UP)r   r   ra   r   s       rf   server_statuszWebInterface.server_status  s0    ( 6S!!/2%F4I4IJr   get_exports_tablec                     |j                  d      sg d}t        ||d      |d<   t        j                  ||||      }|S )a\	   Get the data on the Tautulli export tables.

            ```
            Required parameters:
                section_id (str):               The id of the Plex library section, OR
                user_id (str):                  The id of the Plex user, OR
                rating_key (str):               The rating key of the exported item

            Optional parameters:
                order_column (str):             "added_at", "sort_title", "container", "bitrate", "video_codec",
                                                "video_resolution", "video_framerate", "audio_codec", "audio_channels",
                                                "file_size", "last_played", "play_count"
                order_dir (str):                "desc" or "asc"
                start (int):                    Row to start from, 0
                length (int):                   Number of items to return, 25
                search (str):                   A string to search for, "Thrones"

            Returns:
                json:
                    {"draw": 1,
                     "recordsTotal": 10,
                     "recordsFiltered": 3,
                     "data":
                        [{"timestamp": 1602823644,
                          "art_level": 0,
                          "complete": 1,
                          "custom_fields": "",
                          "exists": true,
                          "export_id": 42,
                          "exported_items": 28,
                          "file_format": "json",
                          "file_size": 57793562,
                          "filename": null,
                          "individual_files": 1,
                          "logo_level": 0,
                          "media_info_level": 1,
                          "media_type": "collection",
                          "media_type_title": "Collection",
                          "metadata_level": 1,
                          "rating_key": null,
                          "section_id": 1,
                          "thumb_level": 2,
                          "title": "Library - Movies - Collection [1]",
                          "total_items": 28,
                          "user_id": null
                          },
                         {...},
                         {...}
                         ]
                     }
            ```
        r/  )
r  )media_type_titleTT)r  TTr  )file_formatTT)metadata_levelTT)media_info_levelTT)custom_fieldsTTr  )completeTFr  )rQ  r  r  ra   )rw   r0   r   get_export_datatable)r   rQ  r  r  ra   rA  r   s          rf   get_export_listzWebInterface.get_export_list  sO    v zz+&	5J #8
K"XF;..*7>:D6<>
 r   c                 l    t         j                  j                  }|dk(  rd}t        dd|||||||	      S )Nphoto_album
photoalbumzexport_modal.htmlzExport Metadata)	r`   r   rQ  r  r  r  sub_media_typeexport_typefile_formats)r   ExportFILE_FORMATSrg   )	r   rQ  r  r  r  r  r  ra   r  s	            rf   export_metadata_modalz"WebInterface.export_metadata_modalb  sG    
  33&%J,?GX)3WQ[)3N*5LR 	Rr   c                 4    t        j                  ||      }|S )ae   Get a list of available custom export fields.

            ```
            Required parameters:
                media_type (str):          The media type of the fields to return

            Optional parameters:
                sub_media_type (str):      The child media type for
                                           collections (movie, show, artist, album, photoalbum),
                                           or playlists (video, audio, photo)

            Returns:
                json:
                    {"metadata_fields":
                        [{"field": "addedAt", "level": 1},
                         ...
                         ],
                     "media_info_fields":
                        [{"field": "media.aspectRatio", "level": 1},
                         ...
                         ]
                    }
            ```
        )r  r  )r   get_custom_fields)r   r  r  ra   r  s        rf   get_export_fieldszWebInterface.get_export_fieldsq  s"    : !22jBPR r   c                     t        j                  |      }t        j                  |||||||||	|
||      j	                         }t        |t              rdd|dS d|dS )a   Export library or media metadata to a file

            ```
            Required parameters:
                section_id (int):          The section id of the library items to export, OR
                user_id (int):             The user id of the playlist items to export, OR
                rating_key (int):          The rating key of the media item to export

            Optional parameters:
                file_format (str):         csv (default), json, xml, or m3u
                metadata_level (int):      The level of metadata to export (default 1)
                media_info_level (int):    The level of media info to export (default 1)
                thumb_level (int):         The level of poster/cover images to export (default 0)
                art_level (int):           The level of background artwork images to export (default 0)
                logo_level (int):          The level of logo images to export (default 0)
                custom_fields (str):       Comma separated list of custom fields to export
                                           in addition to the export level selected
                export_type (str):         'collection' or 'playlist' for library/user export,
                                           otherwise default to all library items
                individual_files (bool):   Export each item as an individual file for library/user export.

            Returns:
                json:
                    {"export_id": 1}
            ```
        )rQ  r  r  r  r  r  thumb_level	art_level
logo_levelr  r  individual_filesr   zMetadata export has started.)r   r   	export_idr   r   )r   r   r   r  exportr   r  )r   rQ  r  r  r  r  r  r  r  r  r  r  r  ra   r   s                  rf   export_metadatazWebInterface.export_metadata  s}    D #,,-=>J)0,6-80>2B-8+4,6/<-82BD EKFH 	 fc"'4Raghh%&99r   c                 p   t        j                  |      }|r|d   dk(  r|d   r|d   st        j                  |d   |d   |d         }|d	   d
k(  r~t        |dd      5 }t	        j
                  |      }ddj                  |j                        z   dz   dj                  d |D              z   dz   }d}ddd       dj                        S |d	   dk(  rt        ||d   d      S |d	   dk(  rt        ||d   d      S |d	   dk(  rt        ||d   d      S y|r|j                  d      dk(  rd }	n1|r|j                  d      d!k(  rd"}	n|r|j                  d      sd#}	nd$}	dt        j                  j                  d%<   t        j                  d&|	d'      j!                  d      S # 1 sw Y   xY w)(a&   Download an exported metadata file

            ```
            Required parameters:
                export_id (int):          The row id of the exported file to view

            Optional parameters:
                None

            Returns:
                download
            ```
        r  r  rj   r  r  r   r  r   r  csvr  r  r  z<table><tr><th>z	</th><th>z</th></tr><tr>z	</tr><tr>c              3   f   K   | ])  }d dj                  |j                               z   dz    + yw)z<td>z	</td><td>z</td>N)rN   values)r   r  s     rf   r   z+WebInterface.view_export.<locals>.<genexpr>  s2      -eVY)9)9#**,)G G' Q-es   /1z</tr></table>a  <style>body {margin: 0;}table {border-collapse: collapse; overflow-y: auto; height: 100px;} th {position: sticky; top: 0; background: #ddd; box-shadow: inset 1px 1px #000, 0 1px #000;}td {box-shadow: inset 1px -1px #000;}th, td {padding: 3px; white-space: nowrap;}</style>Nz{style}<pre>{table}</pre>)styletabler  r  )r  r  r'  zapplication/xml;charset=UTF-8m3uztext/plain;charset=UTF-8r    Export is still being processed.ri   Export failed to process.Export file does not exist.Invalid export_id provided.r  r   r   )r   
get_exportget_export_filepathr   r  
DictReaderrN   
fieldnamesr~   r   rw   r   rR  r  r  r  r  )
r   r  ra   r   filepathinfilereaderr  r  r  s
             rf   view_exportzWebInterface.view_export  s     $$y9fZ(A-&2B6RdKe33F7OVKEXZ`akZlmHm$-(C': 'f ^^F3F-',,V->->?@,- (,, -e]c-e ee
 ,,E'E' 399U9SS&&0!(
1CRrss&%/!(
1CRqrr&%/!(
1CRlmm 0 &**Z0A58FJJz2b81

8 4338XH%%n5::SABII'RRI' 's   "AF,,F5c                 :   t        j                  |      }|r|d   dk(  r|d   r|d   s|d   s
|d   s|d   rt        j                  |d	   |d
         }t        j                  |      }dj	                  |      }t               }t        j                  |d      }t        j                  ||       |j                          t        |j                         dd|      S t        j                  |d	   |d
   |d         }	t        |	|d         S |r|j                  d      dk(  rd}
n1|r|j                  d      dk(  rd}
n|r|j                  d      sd}
nd}
dt         j"                  j$                  d<   t'        j(                  d|
d      j+                  d      S )a*   Download an exported metadata file

            ```
            Required parameters:
                export_id (int):          The row id of the exported file to download

            Optional parameters:
                None

            Returns:
                download
            ```
        r  r  rj   r  r  r  r  r  r   r  z{}.zipr  zapplication/zip
attachment)r  dispositionr  r   r  r   r  ri   r  r  r  r  r  r   r   r  )r   r  format_export_directoryget_export_dirpathr~   r   zipfileZipFiler   zipdirr  r   getvaluer  r   rw   r   rR  r  r  r  r  )r   r  ra   r   	directorydirpathzip_filenamebuffertemp_zipr  r  s              rf   download_exportzWebInterface.download_export  s   " $$y9fZ(A-&2Bm${(;vl?SW]^pWq$<<VG_fU`Nab	"55i@'y9 "??637w1 $V__%6EV1=LR R $77wP[I\^deo^pq%hVJ5GHH &**Z0A58FJJz2b81

8 4338XH%%n5::SABII'RRr   c                     t        j                  |      r t        j                         }|rdddS dddS t        j                  |      }|rdddS dddS )	aX   Delete exports from Tautulli.

            ```
            Required parameters:
                export_id (int):          The row id of the exported file to delete

            Optional parameters:
                delete_all (bool):        'true' to delete all exported files

            Returns:
                None
            ```
        r   z!All exports deleted successfully.r   r   zFailed to delete all exports.r  zExport deleted successfully.zFailed to delete export.)r   r   r   delete_all_exportsdelete_export)r   r  r!  ra   r   s        rf   r  zWebInterface.delete_export5  sh    $ Z(002F"+8[\\")6UVV ++i@F"+8VWW")6PQQr   c                 6    dt        j                         z   dz   S )Nz<pre>z</pre>)r   build_export_docsr   s     rf   exporter_docszWebInterface.exporter_docsV  s     3355@@r   )NN)TTr   )r   r   r   )r   F)r  r9  
   )rx  r   )N10)NNNr   )NF)NNN)NNr  )NNNN)30Nr9  N)12r9  NN)r  r9  NN)r  N)r   )r   r   r   r   r   )Nr  zTest notificationr   rJ   )NNNNFNr   )NNF)NNr   )NNNr   r   FF)FFF)NNNNN)r   N)r   r   )NNi  i  d   000000r   pngNFF)r   r   F)r   r   NN)F)rx  rx  r   r   )
Nr  r9  r   r  r   NNNN)Nr   r   r   r   )NNNFF)NNNNNN)NNNr  rj   rj   r   r   r   r   r  F)r   r   r   r6   authr   r   r   r7   r   r8   r   toolsjson_outr   r-   r   r   r   r   r   r   r  r  r  r  r!  r&  r(  r   r1   rD  rI  rL  rX  r]  re  rl  rq  rw  ry  r  r  r  r  r  r  r  r  r  r  r  r  r'   r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r-  r2  r5  r:  r>  r   rC  rK  rN  rR  rV  rY  r\  r_  rb  re  ri  rk  rm  ro  rq  rx  r}  r  r  r  r  r  r  r  r  r  r  r   r  r  rM  rS  rX  r?  r^  ra  rc  rf  rt  rv  rx  r{  r~  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  r  rL  rO  rR  r]  rv  r~  r  rB  r_  r  r  r  r  r   r'  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r$  r&  r  r,  r2  r  r=  r?  rA  rD  r  r  rK  rN  r   rS  rV  rX  rZ  r\  rl  rp  rs  rv  rv  r  r|  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r   r  r  r  r  r  r  r  r  r  rJ   r   rf   r   r      s2   DI __]F  F __7#$` % `$ __^^7#$ %   __^^7#$ !  ! %  ! L __]	W  	W __^^]Z    > __]	T  	T __]	`  	` __^^7#$ZR  %  R2 __7#$
\ % 
\ __]_  _ __]j  j __]R  R __^^7#$Zg  %  g __^^7#$ZL  %  L __^^7#$ZR  %  R __]Q  Q __^^]^#$Q %    
Qf __^^7#$^!" #  %  
8 __^^7#$W %  W __]r  r, __7#$
r % 
r __7#$Z#3  % #3J __]n  n  __]l  l  __]s  s  __]r  r  __^^7#$Z\  %  \| __^^]%& '   B __^^]#$! %   !F __^^7#$# %  #: __^^7#$Z*Q  %  *QX __^^7#$Z,Z  %  ,Z\ __^^7#$Z*T  %  *TX __^^7#$Zf  %  f2 __^^7#$Zf  %  f2 __^^7#$Zo  %  o2 __^^7#$Z[  %  [: __^^7#$Z %  Z __]I  I __^^]^ R !    
Rh __^^7#$S %  S __]Z  Z" __7#$	} % 	} __7#$Z%&  % %&N __]n  n  __]k  k  __]s  s  __^^]^ZA     
AF __^^]^Z:     
:x __^^7#$ZT  %  T, __^^7#$Z+K  %  +KZ __^^7#$Z+T  %  +TZ __^^7#$Z*T  %  *TX __^^7#$ZU  %  U0 __^^7#$ZU  %  U0 __^^7#$Ze  %  e8 __]\  \ __^^]^Zl     
l\ __]r  r __^^] K !   KZ __]M  M __^^7#$J   %  J: __]K  K __^^]^Z     
0 __^^]Z&    &P __^^]Z&    &P __^^]Z&    &P __^^]Z&    &P __^^]Z&    &P __^^]Z&    &P __^^]Z%    %N __^^]Z      D __^^]Z%    %N __^^]Z%    %N __^^]Z%    %N __^^]Z%    %N __]k  k __]O  O __^^^]    $ __^^7#$"#T $ %  T6 __7#$e % e __7#$? % ?B __^^7#$Z   %   D __^^7#$^Z7!   %  
7!r __^^7#$^Z9   %  
9v __^^7#$Z/  %  /* __^^7#$Z/  %  /* __^^7#$Z/  %  /* __^^7#$2 %  2( __7#$	? % 	? __]"  " __7#$) % )" __7#$e % e* __^^7#$PC %  PCd __^^7#$+ %  + __^^7#$ %  
 __^^7#$I %  I __^^7#$K %  K __7#$H % H __7#$D % D __7#$M % M __^^7#$R %  R __^^7#$M %  M __^^7#$Z  %  2 __7#$[ % [ __^^7#$ZP  %  P( __^^7#$Z)  %  )V __7#$l % l __^^7#$ZW  %  W* __^^7#$ZX  %  X@ __7#$g % g( __^^7#$Z  %  2 __^^7#$%N %  %NN __^^7#$ %   __^^7#$W %  W __7#$G % G __^^7#$R %  R __7#$ %  __^^7#$U %  U __7#$' % ' __7#$^ % ^ __^^7#$
L %  
L __7#$X % X
 __^^7#$ZS  %  S* __^^7#$ZN  %  N, X__2*D12__^^7#$Zdi@A`S  %   3
`SD __^^7#$Z,C  %  ,C\ __7#$	 % 	 __7#$i % i __^^7#$
C %  
C __^^7#$ZUV49X  %  Xt __^^7#$Z  %  < __7#$Z  % * __^^7#$ %    __^^7#$ZA  %  AFf __7#$E % E __7#$A % A __7#$? % ? __7#$	O % 	O __7#$\ % \ __7#$D % D
 __7#$k % k" __],8  ,8\ __]
m  
m __]e  e __]n  n __]l  l __^^7#$Z4W  %  4Wl __^^7#$Z.Q  %  .Q` __^^7#$%&J ' %  JX __^^7#$%&#J ' %  #JJ __	3 	3  PTRW@EwM !wMr __ 4 __7#$Z=  % =, __7#$ZA  % A0 __7#$Z\  % \@ __7#$Z>  % >< __^^7#$Z2  %  2 __^^7#$Z2  %  20 __^^7#$ZU  %  U< __^^7#$ZS  %  S> __]X  X __^^7#$h#  %  #J __]x  x( __7#$r % r __^^7#$Z#3  %  #3L __^^7#$Z  %  4 __^^7#$Z  %  4 __^^7#$	 %  	 __^^7#$nY  %  Yv __^^7#$"#X $ %  Xt __^^7#$	I %  	I __^^7#$	I %  	I __^^7#$	H %  	H __^^7#$G %  G __^^7#$D %  D __^^7#$Z  %  < __^^7#$Z  %  6 __^^7#$Z  %  , __^^]ZnQ    nQ`	 __^^7#$o"  %  "H __^^7#$k,  %  ,\ __^^7#$^Z2   %  
2h __^^7#$Q %  Q __^^7#$ZFM>@IMq  %  qf __7#$h()  % ()X __l l __^^7#$Z*  %  *6 __^^7#$Z  %  > __^^]Z&j    &jP __^^]Z    B __^^7#$( %  ( __^^7#$Z  %  4 __7#$_ % _ __^^7#$ZR  %  R( __^^7#$Z'  %  'R __7#$Y % Y __^^7#$ZO  %  O* __^^7#$ZP  %  P0 __^^7#$#P %  #PJ __9 9& __]  0 __7#$- % - __7#$LP+0P % P< __7#$M % M __^^Z"   "H __^^Z   , __^^7#$!"J # %  JX __7#$NR>B*.R % R __^^7#$Z  %  < __^^7#$ZZ_;<?@NS/:  %  /:b __7#$8S % 8St __7#$Z,S  % ,S\ __^^7#$ZR  %  R: __7#$A % Ar   r   )Zr  r  ior   r   r  r{   rL   r  r@  r;  rn   r  r
  urllib.parser   r   cherrypy.lib.staticr   r   r   cherrypy._cperrorr	   hashing_passwordsr
   mako.lookupr   mako.templater]   mako.exceptionsr9  r=  rW  rX  rP   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r    r!   r"   r#   r$   r%   r&   r'   r(   r)   r*   plexpy.api2r+   plexpy.helpersr,   r-   r.   r/   r0   r1   plexpy.sessionr2   r3   r4   r5   plexpy.webauthr6   r7   r8   r9   r:   r=  r<   r>   rK   rg   rT   objectr   r   rJ   r   rf   <module>r1     s  $  
    	   
   "  I I & ' &    v  " %            %   '   # #       t t k k \ \	??i__  >.
@	P6 	P@oA6 @oAr   