
    g^              	          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Zd dl	Z	d dl
Z
d dlZd dlmZ d dlmZmZ d dlmZ d dlmZ d dlmZmZ d dlmZ d dlZd dlmZ d d	lmZmZmZ 	 d d
l m Z   ejD                  d      Z#i ddddddddddddddddddddd d!d"d#d$d%d&d'd(d)d*d+d,d-d.d/d0Z$e$jK                         D  ci c]  \  } }|| 
 c}} Z&i d1d d2dd,dd3dd4dd5dd6dd7dd8dd9dd:d!d;d#d<d.d=d>d?d@dAdBdCdDi dEdFdGdHdIdJdKdLdMdNdOdPdQdRdSdTdUdVdWdXdYdZd[d\d]d^d_d`dadbdcdddedfdgdhdidjZ'e'jK                         D  ci c]  \  } }|| 
 c}} Z(i Z) G dk dlejT                        Z+dm Z,dn Z-do Z.dp Z/dq Z0ddrZ1ds Z2dt Z3du Z4dv Z5dw Z6ddxZ7dy Z8ddzZ9dd{Z:	 	 dd|Z;	 	 dd}Z<dd~Z=ddZ>ddZ?d Z@d ZAd ZBddZCddZDd ZEd ZFd ZGg dZHej                  dk\  reHj                  g d       eHD cg c]  \  }} eK|       d eK|        c}}ZL ej                  ddj                  eL       d      ZOd ZPy# e!$ r dZ Y w xY wc c}} w c c}} w c c}}w )    N)deque)datetime	timedelta)getpass)sha1)EventThread)quote)_codes)
BadRequestNotFoundUnauthorized)tqdmplexapimovie   show   season   episode   trailer   comic   person   artist   album	   track
   picture   clip   photo   
photoalbum   playlist   playlistFolder   
collection   *   i  )optimizedVersionuserPlaylistItemtaggenredirectorwriterroleproducercountrychapterreviewlabelmarkermediaProcessingTargetmake   model   aperture   exposure   iso   lens   device   autotag   moodi,  stylei-  formati.  similari1  concerti2  banneri7  posteri8  arti9  guidi:  ratingImagei<  themei=  studioi>  networki?  iB  iC  i  )showOrdering	clearLogoplacec                   $    e Zd ZdZddZd Zd Zy)SecretsFilterz! Logging filter to hide secrets. Nc                 *    |xs
 t               | _        y N)setsecrets)selfrg   s     "/opt/Tautulli/lib/plexapi/utils.py__init__zSecretsFilter.__init__j   s    '#%    c                 J    | |dk7  r| j                   j                  |       |S N )rg   add)rh   secrets     ri   
add_secretzSecretsFilter.add_secretm   s%    &B,LLV$rk   c                     t        |j                        }t        t        |            D ]?  }t	        ||   t
              s| j                  D ]  }||   j                  |d      ||<    A t        |      |_        y)Nz<hidden>T)	listargsrangelen
isinstancestrrg   replacetuple)rh   record	cleanargsirp   s        ri   filterzSecretsFilter.filterr   sx    %	s9~& 	LA)A,,"ll LF#,Q<#7#7
#KIaLL	L I&rk   re   )__name__
__module____qualname____doc__rj   rq   r~    rk   ri   rc   rc   g   s    +(
rk   rc   c                 l   t        | dt        | d| j                              }|r| j                   d| n| j                  }t        | dd      r| d}nt        | dd      r| d}|t        v r<t	        d	| j
                   d
| j                   d| dt        |   j
                         | t        |<   | S )z Registry of library types we may come across when parsing XML. This allows us to
        define a few helper functions to dynamically convert the XML into objects. See
        buildItem() below for an example.
    
STREAMTYPETAGTYPE._SESSIONTYPENz.session_HISTORYTYPEz.historyz Ambiguous PlexObject definition z(tag=z, type=z) with )getattrTYPETAGPLEXOBJECTS	Exceptionr   )clsetypeehashs      ri   registerPlexObjectr   |   s    
 CwsIsxx'HIE$)swwiq swwEsND)'"	nd	+'":3<<.cggYV]^c]d e  +E 2 ; ;<> ? 	?KJrk   c                     t         j                  |       }||S d| v r"| j                  dd      d   } t        | |      S t         j                  |      S )z Return the PlexObject class for the specified ehash. This recursively looks up the class
        with the highest specificity, falling back to the default class if not found.
    r   r   r   )default)r   getrsplitgetPlexObject)r   r   r   s      ri   r   r      sT     //%
 C


e|S!$Q'UG44??7##rk   c                     ||S | t         k(  r|dv ry|dv ryt        |      | t        t        fv r		  | |      S  | |      S # t        $ r t        d      cY S w xY w)aB   Cast the specified value to the specified type (returned by func). Currently this
        only support str, int, float, bool. Should be extended if needed.

        Parameters:
            func (func): Callback function to used cast to type (int, bool, float).
            value (any): value to be cast and returned.
    )r   T1trueT)r   F0falseFnan)bool
ValueErrorintfloat)funcvalues     ri   castr      sz     }t|**,,U|	 ; ;  	 <	 s   A AAc           	          | syg }t        | d       D ]1  }t        | |         }|j                  | dt        |d              3 ddj	                  |       S )z Returns a query string (uses for HTTP URLs) where only the value is URL encoded.
        Example return value: '?genre=action&type=1337'.

        Parameters:
            args (dict): Arguments to include in query string.
    rn   c                 "    | j                         S re   lowerxs    ri   <lambda>zjoinArgs.<locals>.<lambda>   s    !'') rk   )key=)safe?&)sortedrx   appendr
   join)rt   arglistr   r   s       ri   joinArgsr      sk     Gd 34 9DI#ae" 56789 sxx !""rk   c                 4    | d   j                         | dd  z   S )Nr   r   r   ss    ri   
lowerFirstr      s    Q4::<!AB%rk   c                 r   	 |j                  |d      }|d   }t        |      dk(  r|d   nd}t        | t              r| |   }nZt        | t              r| t        |         }n;t        | t              r| t        |         }nt        | t              rt        | |      }|rt        |||      S S #  |cY S xY w)ag   Returns the value at the specified attrstr location within a nested tree of
        dicts, lists, tuples, functions, classes, etc. The lookup is done recursively
        for each key in attrstr (split by by the delimiter) This function is heavily
        influenced by the lookups used in Django templates.

        Parameters:
            obj (any): Object to start the lookup in (dict, obj, list, tuple, etc).
            attrstr (str): String to lookup (ex: 'foo.bar.baz.value')
            default (any): Default value to return if not found.
            delim (str): Delimiter separating keys in attrstr.
    r   r   r   N)
splitrv   rw   dictrs   r   rz   objectr   rget)objattrstrr   delimpartsattrr   s          ri   r   r      s    eQ'Qx!%jAo%(4c4 IET"D	NEU#D	NEV$C&Ew77s   B+B0 .B0 0B6c                     t        |       } 	 t        |    S # t        $ r9 | t        D cg c]  }t        |       nc c}w c}v r| cY S t	        d|        dw xY w)z Returns the integer value of the library string type.

        Parameters:
            libtype (str): LibType to lookup (See :data:`~plexapi.utils.SEARCHTYPES`)

        Raises:
            :exc:`~plexapi.exceptions.NotFound`: Unknown libtype
    Unknown libtype: N)rx   SEARCHTYPESKeyErrorREVERSESEARCHTYPESr   )libtypeks     ri   
searchTyper      sd     'lG@7## @'9:!s1v:::N*7)454?@    A;AAc                     	 t         t        |          S # t        t        f$ r | t        v r| cY S t        d|        dw xY w)z Returns the string value of the library type.

        Parameters:
            libtype (int): Integer value of the library type.

        Raises:
            :exc:`~plexapi.exceptions.NotFound`: Unknown libtype
    r   N)r   r   r   r   r   r   )r   s    ri   reverseSearchTyper      sO    @!#g,//j! @k!N*7)454?@    ??c                     t        |       } 	 t        |    S # t        $ r9 | t        D cg c]  }t        |       nc c}w c}v r| cY S t	        d|        dw xY w)z Returns the integer value of the library tag type.

        Parameters:
            tag (str): Tag to lookup (See :data:`~plexapi.utils.TAGTYPES`)

        Raises:
            :exc:`~plexapi.exceptions.NotFound`: Unknown tag
    Unknown tag: N)rx   TAGTYPESr   REVERSETAGTYPESr   )r6   r   s     ri   tagTyper   
  s^     c(C8} 8?3a3q6333Jse,-478r   c                     	 t         t        |          S # t        t        f$ r | t        v r| cY S t        d|        dw xY w)z Returns the string value of the library tag type.

        Parameters:
            tag (int): Integer value of the library tag type.

        Raises:
            :exc:`~plexapi.exceptions.NotFound`: Unknown tag
    r   N)r   r   r   r   r   r   )r6   s    ri   reverseTagTyper     sJ    8s3x((j! 8(?Jse,-478r   c           
         g g }}t               }|D ]g  }||t        |      gz  }|j                  d       |j                  t        | |t	        |                   d|d   _        |d   j                          i |j                         s9t        d |D              rn&t        j                  d       |j                         s9|D cg c]  }||	 c}S c c}w )aO   Returns the result of <callback> for each set of `*args` in listargs. Each call
        to <callback> is called concurrently in their own separate threads.

        Parameters:
            callback (func): Callback function to apply to each set of `*args`.
            listargs (list): List of lists; `*args` to pass each thread.
    N)job_is_done_event)targetrt   kwargsTc              3   >   K   | ]  }|j                            y wre   )is_alive).0ts     ri   	<genexpr>zthreaded.<locals>.<genexpr>>  s     1A1::<1s   g?)r   rv   r   r	   r   daemonstartis_setalltimesleep)callbacklistargsthreadsresultsr   rt   rs          ri   threadedr   -  s     2WG #g,''tvXDXiAjkl!  &&(111

4  &&(
 0!!-A000s   CCc                    | ;|r	 t        j                  | |      S 	 t        |       } 	 t        j                  |       S | S # t        $ r t        j	                  d| |       Y yw xY w# t        $ r t        j	                  d|        Y yw xY w# t        t        t        f$ rL 	 t        j                  d      t        |       z   cY S # t        $ r t        j	                  d|        Y Y yw xY ww xY w)z Returns a datetime object from the specified value.

        Parameters:
            value (str): value to return as a datetime
            format (str): Format to pass strftime (optional; if value is a str).
    NzCFailed to parse "%s" to datetime as format "%s", defaulting to NonezAFailed to parse "%s" to datetime as timestamp, defaulting to Noner   )secondszQFailed to parse "%s" to datetime as timestamp (out-of-bounds), defaulting to None)
r   strptimer   loginfor   fromtimestampOSErrorOverflowErrorr   )r   rT   s     ri   
toDatetimer   E  s     ((77
E
 --e44 L#  ^`egmn  \^cd
 ]J7   #11!4y7OOO$  HHprwx  sQ   A  A& B   A#"A#&BBC+ !CC+C'#C+&C''C+c           	         t        |       } | dk  rdt        t        |             z   S t        | d      \  }}t        |d      \  }}t        |d      \  }}t        |d      \  }}|dk(  rdn| d|dkD  rd	nd d
|dd|dd|dd|dz   S )z Returns human readable time duration [D day[s], ]HH:MM:SS.UUU from milliseconds.

        Parameters:
            milliseconds (str, int): time duration in milliseconds.
    r   -i  <      rn   z dayr   r   , 02d:r   03d)r   millisecondToHumanstrabsdivmod)millisecondssecsmsminshoursdayss         ri   r   r   d  s     |$La*3|+<===lD)HD"b!JD$r"KE4#KD%!)BD6TAXc2-Fb!IPUVY{Z[\`ad[eefgklofppqrtuxqyMzzzrk   c                     | xs d} |xs t         }| j                  |      D cg c]  }|dk7  s	 ||       c}S c c}w )a$   Returns a list of strings from the specified value.

        Parameters:
            value (str): comma delimited string to convert to list.
            itemcast (func): Function to cast each list item to (default str).
            delim (str): string delimiter (optional; default ',').
    rn   )rx   r   )r   itemcastr   items       ri   toListr
  t  s=     KRE3H',{{5'9HtTRZHTNHHHs   
<
<c                     dt         j                   t         j                   t        j                  d|       j                  dd      j                         }dj                  fd|D              }|S )Nz-_.()[] NFKDASCIIignorern   c              3   .   K   | ]  }|v r|n  y wre   r   )r   cry   	whitelists     ri   r   z cleanFilename.<locals>.<genexpr>  s     ZAANq?Zs   )stringascii_lettersdigitsunicodedata	normalizeencodedecoder   )filenamery   cleaned_filenamer  s    ` @ri   cleanFilenamer    sc    6//0@I",,VX>EEgxX__awwZIYZZrk   c           
         i }| j                         D ]  }d}|j                         D ]^  }	|j                  r|j                  }|	j                  s(d|	j                   d|	j                  j                          d|j                   }` |sy|C|j                         }
d|j                  d    d|
 dt        t        j                                }| j                  |||||      }t        || j                  |      }||d	|d
<    |S )a   Helper to download a bif image or thumb.url from plex.server.sessions.

       Parameters:
           filename (str): default to None,
           height (int): Height of the image.
           width (int): width of the image.
           opacity (int): Opacity of the resulting image (possibly deprecated).
           saturation (int): Saturating of the resulting image.

       Returns:
            {'hellowlol': {'filepath': '<filepath>', 'url': 'http://<url>'},
            {'<username>': {filepath, url}}, ...
    Nz/library/parts/z	/indexes//session_transcode_r   _)r  )filepathurlusername)sessions	iterPartsthumbindexesidr   
viewOffset_prettyfilename	usernamesr   r   transcodeImagedownload_token)serverr  heightwidthopacity
saturationr   mediar!  part
prettynamer   s               ri   downloadSessionImagesr6    s    D" BOO% 	dD{{kk||'y	$,,:L:L:N9OqQVQaQaPbc		d
 "224
/0B/C1ZLPQRUVZV_V_VaRbQcd''VUGZPCV]]XFH,4SADB Krk   c	                    |xs t        j                         }d|i}	|j                  | |	d      }
|
j                  dvrt	        j                  |
j                        d   }|
j
                  j                  dd      }d|
j                   d	| d
|
j                   d| }|
j                  dk(  rt        |      |
j                  dk(  rt        |      t        |      |xs t        j                         }t        j                  |d       |sV|
j                  j                  d      r;t        j                   d|
j                  j                  d            }|d   r|d   nd}t        j"                  j%                  |      }t        j"                  j'                  ||      }t        j"                  j)                  |      d   }|s8|
j                  j                  d      }|rd|v r||j+                  d      d   z  }|rt,        j/                  d|       |S t,        j1                  d|       |r:t2        r4t5        |
j                  j                  dd            }t3        dd||      }t7        |d      5 }|
j9                  |      D ]7  }|j;                  |       |st2        sj=                  t?        |             9 	 ddd       |rt2        rjA                          |jC                  d      r4|r2tE        jF                  |d      5 }|jI                  |       ddd       |S |S # 1 sw Y   hxY w# 1 sw Y   |S xY w)a   Helper to download a thumb, videofile or other media item. Returns the local
        path to the downloaded file.

       Parameters:
            url (str): URL where the content be reached.
            token (str): Plex auth token to include in headers.
            filename (str): Filename of the downloaded file, default None.
            savepath (str): Defaults to current working dir.
            chunksize (int): What chunksize read/write at the time.
            mocked (bool): Helper to do everything except write the file.
            unpack (bool): Unpack the zip file.
            showstatus(bool): Display a progressbar.

        Example:
            >>> download(a_episode.getStreamURL(), a_episode.location)
            /path/to/file
    zX-Plex-TokenT)headersstream)rC   rE   rK   r   
 (z) z; i  i  )exist_okzContent-Dispositionzfilename=\"(.+)\"Nr   zcontent-typeimager  r   zMocked download %szDownloading: %szcontent-lengthB)unit
unit_scaletotaldescwb)
chunk_sizezipr   )%requestsSessionr   status_codecodestextry   r!  r   r   r   osgetcwdmakedirsr8  refindallpathbasenamer   splitextr   r   debugr   r   r   openiter_contentwriteupdaterv   closeendswithzipfileZipFile
extractall)r!  tokenr  savepathsession	chunksizeunpackmocked
showstatusr8  responsecodenameerrtextmessagefullpath	extensioncontenttyperB  barhandlechunks                        ri   r,  r,    s   ( +))+Gu%G{{3{=H?299X11215--''c2h**+2hZr(,,q	R3&w''!!S(7##W%% &299;HKK4( ((,,-BC::2H4D4D4H4HI^4_`"*1+8A;4ww)Hww||Hh/H  *2.I&&**>:7k1))#.q11H 		&1 HH)dH$$(()91=>EI	h	 '**i*@ 	'ELLd

3u:&	'' d		F__Xs+ 	(vh'	( O8O' '	( Os$   8)L-"L-)L-L9-L69Mc                    ddl m} ddlm} | rN| j                  rB| j
                  r6t        d| j                   d        || j                  | j
                        S |j                  d      }|j                  d      }|r|rt        d| d        |||      S |j                  d      }|rt        d	        ||
      S t        d      }t        d      }t        d| d        |||      S )aK   Helper function tries to get a MyPlex Account instance by checking
        the the following locations for a username and password. This is
        useful to create user-friendly command line tools.
        1. command-line options (opts).
        2. environment variables and config.ini
        3. Prompt on the command line.
    r   )CONFIG)MyPlexAccountzAuthenticating with Plex.tv as z..zauth.myplex_usernamezauth.myplex_passwordzauth.server_tokenz&Authenticating with Plex.tv with tokenr^  zWhat is your plex.tv username: zWhat is your plex.tv password: )
r   rp  plexapi.myplexrq  r"  passwordprintr   inputr   )optsrp  rq  config_usernameconfig_passwordconfig_tokenr"  rt  s           ri   getMyPlexAccountr{    s     ,$--/bABT]]DMM::jj!78Ojj!78O?//@CD_o>>::12L67<0067H89H	+H:R
898,,rk   c                     ddl m} d| vrt        d      | d   } ||       }|j                  |       |j	                  |j
                         |j                          |j                  |      S )a   Helper function to create a new MyPlexDevice. Returns a new MyPlexDevice instance.

        Parameters:
            headers (dict): Provide the X-Plex- headers for the new device.
                A unique X-Plex-Client-Identifier is required.
            account (MyPlexAccount): The Plex account to create the device on.
            timeout (int): Timeout in seconds to wait for device login.
    r   )MyPlexPinLoginX-Plex-Client-Identifier0The X-Plex-Client-Identifier header is required.)r8  timeout)clientId)rs  r}  r   runlinkpinwaitForLoginrN   )r8  accountr  r}  clientIdentifierpinlogins         ri   createMyPlexDevicer    sm     .!0KLL9:g.HLLL!LL>>#3>44rk   c                 >   ddl m}m} d| vrt        d       || d      }t	        d       t	        |j                  |             |j                  |       |j                          |j                  rt	        d	        ||j                  
      S t	        d       y)a   Helper function for Plex OAuth login. Returns a new MyPlexAccount instance.

        Parameters:
            headers (dict): Provide the X-Plex- headers for the new device.
                A unique X-Plex-Client-Identifier is required.
            forwardUrl (str, optional): The url to redirect the client to after login.
            timeout (int, optional): Timeout in seconds to wait for device login. Default 120 seconds.
    r   )rq  r}  r~  r  T)r8  oauthz#Login to Plex at the following url:r  zLogin successful!rr  zLogin failed.N)	rs  rq  r}  r   ru  oauthUrlr  r  r^  )r8  
forwardUrlr  rq  r}  r  s         ri   	plexOAuthr  /  s     =!0KLLgT:H	
/0	(

J
'(LLL!~~!"8>>22ork   c                    t        |      dk(  r|d   S t                t        |      D ]5  \  }}t        |      r ||      nt	        ||      }t        d| d|        7 t                	 	 t        |  d      t        fddD              r't        t        d j                  d             }||   S |t                 S # t        t        f$ r Y nw xY wn)	zm Command line helper to display a list of choices, asking the
        user to choose one of the options.
    r   r   z  z: c              3   &   K   | ]  }|v  
 y wre   r   )r   r   inps     ri   r   zchoose.<locals>.<genexpr>[  s     6186s   )r   z::r   c                 X    | j                         rt        | j                               S d S re   )stripr   r   s    ri   r   zchoose.<locals>.<lambda>\  s    QWWY3qwwy> D rk   r   )rv   ru  	enumeratecallabler   rv  anyslicemapr   r   r   
IndexError)msgitemsr   indexr}   nameidxr  s          @ri   chooser  J  s    
 5zQQx	Ge$ $q"4.tAwga.>5'D6"#$ 
G
		3%r
#C6%566S!PRUR[R[\_R`abSz!SX&J' 		 s   .AC 7C CCc                     g }| j                         D ]<  }|j                  |j                  |j                  g}||v r|j                  c S ||z  }> t	        d| ddj                  |       d      )z] Return the full agent identifier from a short identifier, name, or confirm full identifier. zCould not find "z" in agents list (r   ))agents
identifiershortIdentifierr  r   r   )sectionagentr  agidentifierss        ri   getAgentIdentifierr  e  s|    Fnn }}b&8&8"''BK== +	
 &ug-@6AR@SSTU
VVrk   c                 h    t        j                  | j                  d            j                  d      S )Nutf-8)base64	b64encoder  r  )rK  s    ri   	base64strr  p  s'    DKK0188AArk   c                       fd}|S )Nc                 H     t        j                          fd       }|S )zThis is a decorator which can be used to mark functions
        as deprecated. It will result in a warning being emitted
        when the function is used.c                      dj                    d d}t        j                  |t               t        j                  |        | i |S )Nz'Call to deprecated function or method "z", r   )category
stacklevel)r   warningswarnDeprecationWarningr   warning)rt   r   r  r   rh  r  s      ri   wrapperz.deprecated.<locals>.decorator.<locals>.wrappery  sI    ;DMM?#gYVWXCMM#(:zRKK(((rk   )	functoolswraps)r   r  rh  r  s   ` ri   	decoratorzdeprecated.<locals>.decoratoru  s'     
		) 
	)
 rk   r   )rh  r  r  s   `` ri   
deprecatedr  t  s    
 rk   c              #      K   t        | g      }|rC|j                         }||j                  |k(  r| |j                  t	        |             |rByyw)z} Iterate through an XML tree using a breadth-first search.
        If tag is specified, only return nodes with that tag.
    N)r   popleftr6   extendrs   )rootr6   queuenodes       ri   
iterXMLBFSr    sJ      4&ME
}};$((c/JT$Z 	 s   AAAc                 8    d }t        j                  | fd|i|S )z Convert an object to a JSON string.

        Parameters:
            obj (object): The object to convert.
            **kwargs (dict): Keyword arguments to pass to ``json.dumps()``.
    c                     t        | t              r| j                         S | j                  j	                         D ci c]  \  }}|j                  d      r|| c}}S c c}}w )Nr  )rw   r   	isoformat__dict__r  
startswith)r   r   vs      ri   	serializeztoJson.<locals>.serialize  sL    c8$==?"!$!3!3!5OAQ\\#=N1OOOs   A!A!r   )jsondumps)r   r   r  s      ri   toJsonr    s#    P ::c79777rk   c                     t        | d      r| j                         S t        | d      5 }|j                         cd d d        S # 1 sw Y   y xY w)Nreadrb)hasattrr  rU  )filefs     ri   
openOrReadr    sB    tVyy{	dD	 Qvvx  s   AAc                 R    t        | j                  d            j                         S )z! Return the SHA1 hash of a guid. r  )r   r  	hexdigest)rZ   s    ri   sha1hashr    s    G$%//11rk   ))r   r    )r&   r(   )r,      )      )      )i  i  )i  i  i   ))i i )i i )i i )i i )i i )i i )i i )i i )i	 i	 )i
 i
 )i i )i i )i i )i i )i i )i i r   [rn   ]c                 .    t         j                  d|       S rm   )_illegal_XML_resubr   s    ri   cleanXMLStringr    s    r1%%rk   )Nr   re   )N,)r  )N   r  d   r  )NNNi   FFF)r$   )Nx   )r   )Qr  r  r  loggingrL  rO  r  sysr   r  r  r[  collectionsr   r   r   r   hashlibr   	threadingr   r	   urllib.parser
   rG  requests.status_codesr   rJ  plexapi.exceptionsr   r   r   r   ImportError	getLoggerr   r   r  r   r   r   r   Filterrc   r   r   r   r   r   r   r   r   r   r   r   r   r   r
  r  r6  r,  r{  r  r  r  r  r  r  r  r  r  r  _illegal_XML_characters
maxunicoder  chr_illegal_XML_rangescompiler   r  r  )r   r  lowhighs   0000ri   <module>r     s       	 	  
      (   #   1 A A g	"Q
A a q	
 q Q a a Q R r B R "   b!" "#$ '* (3'8'8':;tq!ad; &	1&Q& !& 	&
 a& A& & q& q& b& R& b& R& C& S&  !&" #&$ 
3%&& C'&( c)&* s+&, C-&. S/&0 c1&2 s3&4 s5&6 c7&8 c9&: 
3;&< C=&> 3?&@ SA&B cC&D sE&F K&N %-NN$45DAq1a45 GNN *$
$2#  >@$@"8$8"10>{ 
I DG25B PT49L^->5066WB	!82  >>W""	
, /d 	Cz3t9+  "**"''*=">!?qAB&g  D6 <T 6Ns$   .I' I5'I;J'I21I2