Пример #1
0
    def sendCommand(self, command, proxy=None, **params):
        """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query()` to more easily
            send simple commands to the client. Returns an ElementTree object containing
            the response.

            Parameters:
                command (str): Command to be sent in for format '<controller>/<command>'.
                proxy (bool): Set True to proxy this command through the PlexServer.
                **params (dict): Additional GET parameters to include with the command.

            Raises:
                :class:`~plexapi.exceptions.Unsupported`: When we detect the client
                    doesn't support this capability.
        """
        command = command.strip('/')
        controller = command.split('/')[0]
        if controller not in self.protocolCapabilities:
            log.debug('Client %s doesnt support %s controller.'
                      'What your trying might not work' % (self.title, controller))

        params['commandID'] = self._nextCommandId()
        key = '/player/%s%s' % (command, utils.joinArgs(params))
        headers = {'X-Plex-Target-Client-Identifier': self.machineIdentifier}
        proxy = self._proxyThroughServer if proxy is None else proxy
        if proxy:
            return self._server.query(key, headers=headers)
        return self.query(key, headers=headers)
Пример #2
0
    def sendCommand(self, command, proxy=None, **params):
        """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query()` to more easily
            send simple commands to the client. Returns an ElementTree object containing
            the response.

            Parameters:
                command (str): Command to be sent in for format '<controller>/<command>'.
                proxy (bool): Set True to proxy this command through the PlexServer.
                **params (dict): Additional GET parameters to include with the command.

            Raises:
                :class:`~plexapi.exceptions.Unsupported`: When we detect the client
                    doesn't support this capability.
        """
        command = command.strip('/')
        controller = command.split('/')[0]
        if controller not in self.protocolCapabilities:
            log.debug('Client %s doesnt support %s controller.'
                      'What your trying might not work' %
                      (self.title, controller))

        params['commandID'] = self._nextCommandId()
        key = '/player/%s%s' % (command, utils.joinArgs(params))
        headers = {'X-Plex-Target-Client-Identifier': self.machineIdentifier}
        proxy = self._proxyThroughServer if proxy is None else proxy
        if proxy:
            return self._server.query(key, headers=headers)
        return self.query(key, headers=headers)
Пример #3
0
 def clients(self):
     """ Returns list of all :class:`~plexapi.client.PlexClient` objects connected to server. """
     items = []
     cache_resource = None
     from plexapi.myplex import MyPlexResource
     for elem in self.query('/clients'):
         # Some shitty clients dont include a port..
         port = elem.attrib.get('port')
         if port is None:
             log.debug(
                 "%s didn't provide a port. Checking https://plex.tv/devices.xml"
                 % elem.attrib.get('name'))
             data = cache_resource or self._server._session.get(
                 'https://plex.tv/devices.xml?X-Plex-Token=%s' %
                 self.token)  # noqa
             cache_resource = data
             resources = MyPlexResource(self, data)
             for resource in resources:
                 if resource.clientIdentifier == elem.attrib.get(
                         'machineIdentifier'):
                     for conn in resource.connection:
                         if conn.local is True:
                             port = conn.port
                             break
         baseurl = 'http://%s:%s' % (elem.attrib['host'], port)
         items.append(
             PlexClient(baseurl=baseurl,
                        server=self,
                        data=elem,
                        connect=False))
     return items
Пример #4
0
    def query(self, key, method=None, headers=None, timeout=None, **kwargs):
        """ Main method used to handle HTTPS requests to the Plex server. This method helps
            by encoding the response to utf-8 and parsing the returned XML into and
            ElementTree object. Returns None if no data exists in the response.
        """

        url = self.url(key)
        method = method or self._session.get
        timeout = timeout or TIMEOUT
        log.debug('%s %s', method.__name__.upper(), url)
        headers = self._headers(**headers or {})
        response = method(url, headers=headers, timeout=timeout, **kwargs)
        if response.status_code not in (200, 201, 204):
            codename = codes.get(response.status_code)[0]
            errtext = response.text.replace('\n', ' ')
            message = '(%s) %s; %s %s' % (response.status_code, codename,
                                          response.url, errtext)
            if response.status_code == 401:
                raise Unauthorized(message)
            elif response.status_code == 404:
                raise NotFound(message)
            else:
                raise BadRequest(message)
        data = response.text.encode('utf8')
        return ElementTree.fromstring(data) if data.strip() else None
Пример #5
0
def download(url,
             filename=None,
             savepath=None,
             session=None,
             chunksize=4024,
             unpack=False,
             mocked=False):
    """ 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.
            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 evertything except write the file.
            unpack (bool): Unpack the zip file

        Example:
            >>> download(a_episode.getStreamURL(), a_episode.location)
            /path/to/file
    """
    from plexapi import log
    # fetch the data to be saved
    session = session or requests.Session()
    response = session.get(url, stream=True)
    # make sure the savepath directory exists
    savepath = savepath or os.getcwd()
    compat.makedirs(savepath, exist_ok=True)
    # try getting filename from header if not specified in arguments (used for logs, db)
    if not filename and response.headers.get('Content-Disposition'):
        filename = re.findall(r'filename=\"(.+)\"',
                              response.headers.get('Content-Disposition'))
        filename = filename[0] if filename[0] else None
    filename = os.path.basename(filename)
    fullpath = os.path.join(savepath, filename)
    # append file.ext from content-type if not already there
    extension = os.path.splitext(fullpath)[-1]
    if not extension:
        contenttype = response.headers.get('content-type')
        if contenttype and 'image' in contenttype:
            fullpath += contenttype.split('/')[1]
    # check this is a mocked download (testing)
    if mocked:
        log.debug('Mocked download %s', fullpath)
        return fullpath
    # save the file to disk
    log.info('Downloading: %s', fullpath)
    with open(fullpath, 'wb') as handle:
        for chunk in response.iter_content(chunk_size=chunksize):
            handle.write(chunk)
    # check we want to unzip the contents
    if fullpath.endswith('zip') and unpack:
        with zipfile.ZipFile(fullpath, 'r') as handle:
            handle.extractall(savepath)
    # finished; return fillpath
    return fullpath
Пример #6
0
 def _onMessage(self, ws, message):
     """ Called when websocket message is recieved. """
     try:
         data = json.loads(message)['NotificationContainer']
         log.debug('Alert: %s %s %s', *data)
         if self._callback:
             self._callback(data)
     except Exception as err:  # pragma: no cover
         log.error('AlertListener Msg Error: %s', err)
Пример #7
0
 def _onMessage(self, ws, message):
     """ Called when websocket message is recieved. """
     try:
         data = json.loads(message)['NotificationContainer']
         log.debug('Alert: %s %s %s', *data)
         if self._callback:
             self._callback(data)
     except Exception as err:  # pragma: no cover
         log.error('AlertListener Msg Error: %s', err)
Пример #8
0
 def query(self, url, method=None, headers=None, timeout=None, **kwargs):
     method = method or self._session.get
     timeout = timeout or TIMEOUT
     log.debug('%s %s %s', method.__name__.upper(), url, kwargs.get('json', ''))
     headers = self._headers(**headers or {})
     response = method(url, headers=headers, timeout=timeout, **kwargs)
     if response.status_code not in (200, 201, 204):  # pragma: no cover
         codename = codes.get(response.status_code)[0]
         errtext = response.text.replace('\n', ' ')
         raise BadRequest('(%s) %s %s; %s' % (response.status_code, codename, response.url, errtext))
     data = response.text.encode('utf8')
     return ElementTree.fromstring(data) if data.strip() else None
Пример #9
0
    def sendCommand(self, command, proxy=None, **params):
        """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query()` to more easily
            send simple commands to the client. Returns an ElementTree object containing
            the response.

            Parameters:
                command (str): Command to be sent in for format '<controller>/<command>'.
                proxy (bool): Set True to proxy this command through the PlexServer.
                **params (dict): Additional GET parameters to include with the command.

            Raises:
                :class:`plexapi.exceptions.Unsupported`: When we detect the client doesn't support this capability.
        """
        command = command.strip('/')
        controller = command.split('/')[0]
        headers = {'X-Plex-Target-Client-Identifier': self.machineIdentifier}
        if controller not in self.protocolCapabilities:
            log.debug('Client %s doesnt support %s controller.'
                      'What your trying might not work' %
                      (self.title, controller))

        proxy = self._proxyThroughServer if proxy is None else proxy
        query = self._server.query if proxy else self.query

        # Workaround for ptp. See https://github.com/pkkid/python-plexapi/issues/244
        t = time.time()
        if command == 'timeline/poll':
            self._last_call = t
        elif t - self._last_call >= 80 and self.product in (
                'ptp', 'Plex Media Player'):
            self._last_call = t
            self.sendCommand(ClientTimeline.key, wait=0)

        params['commandID'] = self._nextCommandId()
        key = '/player/%s%s' % (command, utils.joinArgs(params))
        log.debug('Plexapi %s command %s  %s '
                  ' key sent: %s' %
                  (self.product, self.title, controller, key))
        try:
            return query(key, headers=headers)
        except ElementTree.ParseError:
            # Workaround for players which don't return valid XML on successful commands
            #   - Plexamp, Plex for Android: `b'OK'`
            #   - Plex for Samsung: `b'<?xml version="1.0"?><Response code="200" status="OK">'`
            if self.product in (
                    'Plexamp',
                    'Plex for Android (TV)',
                    'Plex for Android (Mobile)',
                    'Plex for Samsung',
            ):
                return
            raise
Пример #10
0
 def query(self, url, method=None, headers=None, timeout=None, **kwargs):
     method = method or self._session.get
     timeout = timeout or TIMEOUT
     log.debug('%s %s %s', method.__name__.upper(), url, kwargs.get('json', ''))
     headers = self._headers(**headers or {})
     response = method(url, headers=headers, timeout=timeout, **kwargs)
     if response.status_code not in (200, 201, 204):  # pragma: no cover
         codename = codes.get(response.status_code)[0]
         errtext = response.text.replace('\n', ' ')
         log.warning('BadRequest (%s) %s %s; %s' % (response.status_code, codename, response.url, errtext))
         raise BadRequest('(%s) %s %s; %s' % (response.status_code, codename, response.url, errtext))
     data = response.text.encode('utf8')
     return ElementTree.fromstring(data) if data.strip() else None
Пример #11
0
 def _allowMediaDeletion(self, toggle=False):
     """ Toggle allowMediaDeletion.
         Parameters:
             toggle (bool): True enables Media Deletion
                            False or None disable Media Deletion (Default)
     """
     if self.allowMediaDeletion and toggle is False:
         log.debug(
             'Plex is currently allowed to delete media. Toggling off.')
     elif self.allowMediaDeletion and toggle is True:
         log.debug(
             'Plex is currently allowed to delete media. Toggle set to allow, exiting.'
         )
         raise BadRequest(
             'Plex is currently allowed to delete media. Toggle set to allow, exiting.'
         )
     elif self.allowMediaDeletion is None and toggle is True:
         log.debug(
             'Plex is currently not allowed to delete media. Toggle set to allow.'
         )
     else:
         log.debug(
             'Plex is currently not allowed to delete media. Toggle set to not allow, exiting.'
         )
         raise BadRequest(
             'Plex is currently not allowed to delete media. Toggle set to not allow, exiting.'
         )
     value = 1 if toggle is True else 0
     return self.query('/:/prefs?allowMediaDeletion=%s' % value,
                       self._session.put)
Пример #12
0
 def __getattribute__(self, attr):
     # Dragons inside.. :-/
     value = super(PlexPartialObject, self).__getattribute__(attr)
     # Check a few cases where we dont want to reload
     if attr == 'key' or attr.startswith('_'): return value
     if value not in (None, []): return value
     if self.isFullObject(): return value
     # Log the reload.
     clsname = self.__class__.__name__
     title = self.__dict__.get('title', self.__dict__.get('name'))
     objname = "%s '%s'" % (clsname, title) if title else clsname
     log.debug("Reloading %s for attr '%s'" % (objname, attr))
     # Reload and return the value
     self.reload()
     return super(PlexPartialObject, self).__getattribute__(attr)
Пример #13
0
 def _onMessage(self, *args):
     """ Called when websocket message is received.
         In earlier releases, websocket-client returned a tuple of two parameters: a websocket.app.WebSocketApp
         object and the message as a STR. Current releases appear to only return the message.
         We are assuming the last argument in the tuple is the message.
         This is to support compatibility with current and previous releases of websocket-client.
     """
     message = args[-1]
     try:
         data = json.loads(message)['NotificationContainer']
         log.debug('Alert: %s %s %s', *data)
         if self._callback:
             self._callback(data)
     except Exception as err:  # pragma: no cover
         log.error('AlertListener Msg Error: %s', err)
Пример #14
0
 def __getattribute__(self, attr):
     # Dragons inside.. :-/
     value = super(PlexPartialObject, self).__getattribute__(attr)
     # Check a few cases where we dont want to reload
     if attr == 'key' or attr.startswith('_'): return value
     if value not in (None, []): return value
     if self.isFullObject(): return value
     # Log the reload.
     clsname = self.__class__.__name__
     title = self.__dict__.get('title', self.__dict__.get('name'))
     objname = "%s '%s'" % (clsname, title) if title else clsname
     log.debug("Reloading %s for attr '%s'" % (objname, attr))
     # Reload and return the value
     self.reload()
     return super(PlexPartialObject, self).__getattribute__(attr)
Пример #15
0
 def query(self, url, method=None, headers=None, **kwargs):
     method = method or self._session.get
     delim = '&' if '?' in url else '?'
     url = '%s%sX-Plex-Token=%s' % (url, delim, self._token)
     log.debug('%s %s', method.__name__.upper(), url)
     allheaders = BASE_HEADERS.copy()
     allheaders.update(headers or {})
     response = method(url, headers=allheaders, timeout=TIMEOUT, **kwargs)
     if response.status_code not in (200, 201):
         codename = codes.get(response.status_code)[0]
         log.warn('BadRequest (%s) %s %s' %
                  (response.status_code, codename, response.url))
         raise BadRequest('(%s) %s' % (response.status_code, codename))
     data = response.text.encode('utf8')
     return ElementTree.fromstring(data) if data.strip() else None
Пример #16
0
 def query(self, path, method=None, headers=None, timeout=None, **kwargs):
     """ Main method used to handle HTTPS requests to the Plex client. This method helps
         by encoding the response to utf-8 and parsing the returned XML into and
         ElementTree object. Returns None if no data exists in the response.
     """
     url = self.url(path)
     method = method or self._session.get
     timeout = timeout or TIMEOUT
     log.debug('%s %s', method.__name__.upper(), url)
     headers = self._headers(**headers or {})
     response = method(url, headers=headers, timeout=timeout, **kwargs)
     if response.status_code not in (200, 201):
         codename = codes.get(response.status_code)[0]
         log.warn('BadRequest (%s) %s %s' % (response.status_code, codename, response.url))
         raise BadRequest('(%s) %s' % (response.status_code, codename))
     data = response.text.encode('utf8')
     return ElementTree.fromstring(data) if data.strip() else None
Пример #17
0
 def query(self, key, method=None, headers=None, timeout=None, **kwargs):
     """ Main method used to handle HTTPS requests to the Plex server. This method helps
         by encoding the response to utf-8 and parsing the returned XML into and
         ElementTree object. Returns None if no data exists in the response.
     """
     url = self.url(key)
     method = method or self._session.get
     timeout = timeout or TIMEOUT
     log.debug('%s %s', method.__name__.upper(), url)
     headers = self._headers(**headers or {})
     response = method(url, headers=headers, timeout=timeout, **kwargs)
     if response.status_code not in (200, 201):
         codename = codes.get(response.status_code)[0]
         errtext = response.text.replace('\n', ' ')
         log.warning('BadRequest (%s) %s %s; %s' % (response.status_code, codename, response.url, errtext))
         raise BadRequest('(%s) %s; %s %s' % (response.status_code, codename, response.url, errtext))
     data = response.text.encode('utf8')
     return ElementTree.fromstring(data) if data.strip() else None
Пример #18
0
 def __getattribute__(self, attr):
     # Dragons inside.. :-/
     value = super(PlexPartialObject, self).__getattribute__(attr)
     # Check a few cases where we dont want to reload
     if attr in _DONT_RELOAD_FOR_KEYS: return value
     if attr in _DONT_OVERWRITE_SESSION_KEYS: return value
     if attr in USER_DONT_RELOAD_FOR_KEYS: return value
     if attr.startswith('_'): return value
     if value not in (None, []): return value
     if self.isFullObject(): return value
     # Log the reload.
     clsname = self.__class__.__name__
     title = self.__dict__.get('title', self.__dict__.get('name'))
     objname = "%s '%s'" % (clsname, title) if title else clsname
     log.debug("Reloading %s for attr '%s'", objname, attr)
     # Reload and return the value
     self._reload(_autoReload=True)
     return super(PlexPartialObject, self).__getattribute__(attr)
Пример #19
0
 def _getSectionIds(self, server, sections):
     """ Converts a list of section objects or names to sectionIds needed for library sharing. """
     if not sections: return []
     # Get a list of all section ids for looking up each section.
     allSectionIds = {}
     machineIdentifier = server.machineIdentifier if isinstance(server, PlexServer) else server
     url = self.PLEXSERVERS.replace('{machineId}', machineIdentifier)
     data = self.query(url, self._session.get)
     for elem in data[0]:
         allSectionIds[elem.attrib.get('id', '').lower()] = elem.attrib.get('id')
         allSectionIds[elem.attrib.get('title', '').lower()] = elem.attrib.get('id')
         allSectionIds[elem.attrib.get('key', '').lower()] = elem.attrib.get('id')
     log.debug(allSectionIds)
     # Convert passed in section items to section ids from above lookup
     sectionIds = []
     for section in sections:
         sectionKey = section.key if isinstance(section, LibrarySection) else section
         sectionIds.append(allSectionIds[sectionKey.lower()])
     return sectionIds
Пример #20
0
 def _getSectionIds(self, server, sections):
     """ Converts a list of section objects or names to sectionIds needed for library sharing. """
     if not sections: return []
     # Get a list of all section ids for looking up each section.
     allSectionIds = {}
     machineIdentifier = server.machineIdentifier if isinstance(server, PlexServer) else server
     url = self.PLEXSERVERS.replace('{machineId}', machineIdentifier)
     data = self.query(url, self._session.get)
     for elem in data[0]:
         allSectionIds[elem.attrib.get('id', '').lower()] = elem.attrib.get('id')
         allSectionIds[elem.attrib.get('title', '').lower()] = elem.attrib.get('id')
         allSectionIds[elem.attrib.get('key', '').lower()] = elem.attrib.get('id')
     log.debug(allSectionIds)
     # Convert passed in section items to section ids from above lookup
     sectionIds = []
     for section in sections:
         sectionKey = section.key if isinstance(section, LibrarySection) else section
         sectionIds.append(allSectionIds[sectionKey.lower()])
     return sectionIds
Пример #21
0
    def sendCommand(self, command, proxy=None, **params):
        """ Convenience wrapper around :func:`~plexapi.client.PlexClient.query()` to more easily
            send simple commands to the client. Returns an ElementTree object containing
            the response.

            Parameters:
                command (str): Command to be sent in for format '<controller>/<command>'.
                proxy (bool): Set True to proxy this command through the PlexServer.
                **params (dict): Additional GET parameters to include with the command.

            Raises:
                :class:`plexapi.exceptions.Unsupported`: When we detect the client doesn't support this capability.
        """
        command = command.strip('/')
        controller = command.split('/')[0]
        headers = {'X-Plex-Target-Client-Identifier': self.machineIdentifier}
        if controller not in self.protocolCapabilities:
            log.debug('Client %s doesnt support %s controller.'
                      'What your trying might not work' %
                      (self.title, controller))

        # Workaround for ptp. See https://github.com/pkkid/python-plexapi/issues/244
        t = time.time()
        if t - self._last_call >= 80 and self.product in ('ptp',
                                                          'Plex Media Player'):
            url = '/player/timeline/poll?wait=0&commandID=%s' % self._nextCommandId(
            )
            if proxy:
                self._server.query(url, headers=headers)
            else:
                self.query(url, headers=headers)
            self._last_call = t

        params['commandID'] = self._nextCommandId()
        key = '/player/%s%s' % (command, utils.joinArgs(params))

        proxy = self._proxyThroughServer if proxy is None else proxy

        if proxy:
            return self._server.query(key, headers=headers)
        return self.query(key, headers=headers)
Пример #22
0
def download(url, token, filename=None, savepath=None, session=None, chunksize=4024,
             unpack=False, mocked=False, showstatus=False):
    """ 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 evertything 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
    """

    from plexapi import log
    # fetch the data to be saved
    session = session or requests.Session()
    headers = {'X-Plex-Token': token}
    response = session.get(url, headers=headers, stream=True)
    # make sure the savepath directory exists
    savepath = savepath or os.getcwd()
    compat.makedirs(savepath, exist_ok=True)

    # try getting filename from header if not specified in arguments (used for logs, db)
    if not filename and response.headers.get('Content-Disposition'):
        filename = re.findall(r'filename=\"(.+)\"', response.headers.get('Content-Disposition'))
        filename = filename[0] if filename[0] else None

    filename = os.path.basename(filename)
    fullpath = os.path.join(savepath, filename)
    # append file.ext from content-type if not already there
    extension = os.path.splitext(fullpath)[-1]
    if not extension:
        contenttype = response.headers.get('content-type')
        if contenttype and 'image' in contenttype:
            fullpath += contenttype.split('/')[1]

    # check this is a mocked download (testing)
    if mocked:
        log.debug('Mocked download %s', fullpath)
        return fullpath

    # save the file to disk
    log.info('Downloading: %s', fullpath)
    if showstatus:  # pragma: no cover
        total = int(response.headers.get('content-length', 0))
        bar = tqdm(unit='B', unit_scale=True, total=total, desc=filename)

    with open(fullpath, 'wb') as handle:
        for chunk in response.iter_content(chunk_size=chunksize):
            handle.write(chunk)
            if showstatus:
                bar.update(len(chunk))

    if showstatus:  # pragma: no cover
        bar.close()
    # check we want to unzip the contents
    if fullpath.endswith('zip') and unpack:
        with zipfile.ZipFile(fullpath, 'r') as handle:
            handle.extractall(savepath)

    return fullpath