From dc25ef857fbd2529ceb382317cc0ed4f4a3f90f3 Mon Sep 17 00:00:00 2001 From: JonnyWong16 Date: Sat, 8 Jul 2017 15:07:01 -0700 Subject: [PATCH] Refactor notifier requests and better logging --- plexpy/notifiers.py | 104 ++++++++++++++++++++------------------------ plexpy/request.py | 87 +++++++++++++++++++++++++++++++++++- 2 files changed, 132 insertions(+), 59 deletions(-) diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py index 82bd4777..b2fb03cd 100644 --- a/plexpy/notifiers.py +++ b/plexpy/notifiers.py @@ -686,15 +686,27 @@ class Notifier(object): def notify(self, subject='', body='', action='', **kwargs): pass - def notify_success(self, req=None): - if req is not None: - if req.status_code >= 200 and req.status_code < 300: - logger.info(u"PlexPy Notifiers :: {name} notification sent.".format(name=self.NAME)) - return True - else: - logger.error(u"PlexPy Notifiers :: {name} notification failed: " - "[{r.status_code}] {r.reason}: {r.text}".format(name=self.NAME, r=req)) - return False + def make_request(self, url, method='POST', **kwargs): + response, err_msg, req_msg = request.request_response2(url, method, **kwargs) + + if response and not err_msg: + logger.info(u"PlexPy Notifiers :: {name} notification sent.".format(name=self.NAME)) + return True + + else: + verify_msg = "" + if response is not None and response.status_code >= 400 and response.status_code < 500: + verify_msg = " Verify you notification agent settings are correct." + + logger.error(u"PlexPy Notifiers :: {name} notification failed.{}".format(verify_msg, name=self.NAME)) + + if err_msg: + logger.error(u"PlexPy Notifiers :: {}".format(err_msg)) + + if req_msg: + logger.debug(u"PlexPy Notifiers :: Request response: {}".format(req_msg)) + + return False def return_config_options(self): config_options = [] @@ -784,11 +796,7 @@ class ANDROIDAPP(Notifier): headers = {'Content-Type': 'application/json'} - r = requests.post("https://onesignal.com/api/v1/notifications", headers=headers, json=payload) - - #logger.debug("OneSignal response: {}".format(r.content)) - - return self.notify_success(req=r) + return self.make_request("https://onesignal.com/api/v1/notifications", headers=headers, json=payload) def get_devices(self): db = database.MonitorDatabase() @@ -889,9 +897,7 @@ class BOXCAR(Notifier): 'notification[sound]': self.config['sound'] } - r = requests.post('https://new.boxcar.io/api/notifications', params=data) - - return self.notify_success(req=r) + return self.make_request('https://new.boxcar.io/api/notifications', params=data) def get_sounds(self): sounds = {'': '', @@ -1087,9 +1093,7 @@ class DISCORD(Notifier): headers = {'Content-type': 'application/json'} params = {'wait': True} - r = requests.post(self.config['hook'], params=params, headers=headers, json=data) - - return self.notify_success(req=r) + return self.make_request(self.config['hook'], params=params, headers=headers, json=data) def return_config_options(self): config_option = [{'label': 'Discord Webhook URL', @@ -1530,12 +1534,11 @@ class GROUPME(Notifier): data['attachments'] = [{'type': 'image', 'url': r_content['payload']['picture_url']}] else: - logger.error(u"PlexPy Notifiers :: {name} poster failed: [{r.status_code}] {r.reason}: {r.text}".format(name=self.NAME, r=r)) + logger.error(u"PlexPy Notifiers :: {name} poster failed: [{r.status_code}] {r.reason}".format(name=self.NAME, r=r)) + logger.debug(u"PlexPy Notifiers :: Request response: {}".format(request.server_message(r, True))) return False - r = requests.post('https://api.groupme.com/v3/bots/post', json=data) - - return self.notify_success(req=r) + return self.make_request('https://api.groupme.com/v3/bots/post', json=data) def return_config_options(self): config_option = [{'label': 'GroupMe Access Token', @@ -1731,9 +1734,7 @@ class HIPCHAT(Notifier): headers = {'Content-type': 'application/json'} - r = requests.post(self.config['api_url'], json=data) - - return self.notify_success(req=r) + return self.make_request(self.config['api_url'], json=data) def return_config_options(self): config_option = [{'label': 'Hipchat Custom Integrations Full URL', @@ -1815,10 +1816,8 @@ class IFTTT(Notifier): headers = {'Content-type': 'application/json'} - r = requests.post('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']), - headers=headers, json=data) - - return self.notify_success(req=r) + return self.make_request('https://maker.ifttt.com/trigger/{}/with/key/{}'.format(event, self.config['key']), + headers=headers, json=data) def return_config_options(self): config_option = [{'label': 'Ifttt Maker Channel Key', @@ -1878,8 +1877,8 @@ class JOIN(Notifier): logger.error(u"PlexPy Notifiers :: {name} notification failed: {msg}".format(name=self.NAME, msg=error_msg)) return False else: - logger.error(u"PlexPy Notifiers :: {name} notification failed: " - "[{r.status_code}] {r.reason}: {r.text}".format(name=self.NAME, r=req)) + logger.error(u"PlexPy Notifiers :: {name} notification failed: [{r.status_code}] {r.reason}".format(name=self.NAME, r=r)) + logger.debug(u"PlexPy Notifiers :: Request response: {}".format(request.server_message(r, True))) return False def get_devices(self): @@ -1900,8 +1899,8 @@ class JOIN(Notifier): logger.info(u"PlexPy Notifiers :: Unable to retrieve {name} devices list: {msg}".format(name=self.NAME, msg=error_msg)) return {'': ''} else: - logger.error(u"PlexPy Notifiers :: Unable to retrieve {name} devices list: " - "[{r.status_code}] {r.reason}: {r.text}".format(name=self.NAME, r=req)) + logger.error(u"PlexPy Notifiers :: Unable to retrieve {name} devices list: [{r.status_code}] {r.reason}".format(name=self.NAME, r=r)) + logger.debug(u"PlexPy Notifiers :: Request response: {}".format(request.server_message(r, True))) return {'': ''} else: @@ -2222,9 +2221,7 @@ class PROWL(Notifier): headers = {'Content-type': 'application/x-www-form-urlencoded'} - r = requests.post('https://api.prowlapp.com/publicapi/add', headers=headers, data=data) - - return self.notify_success(req=r) + return self.make_request('https://api.prowlapp.com/publicapi/add', headers=headers, data=data) def return_config_options(self): config_option = [{'label': 'Prowl API Key', @@ -2263,9 +2260,7 @@ class PUSHALOT(Notifier): headers = {'Content-type': 'application/x-www-form-urlencoded'} - r = requests.post('https://pushalot.com/api/sendmessage', headers=headers, data=data) - - return self.notify_success(req=r) + return self.make_request('https://pushalot.com/api/sendmessage', headers=headers, data=data) def return_config_options(self): config_option = [{'label': 'Pushalot API Key', @@ -2307,9 +2302,7 @@ class PUSHBULLET(Notifier): 'Access-Token': self.config['apikey'] } - r = requests.post('https://api.pushbullet.com/v2/pushes', headers=headers, json=data) - - return self.notify_success(req=r) + return self.make_request('https://api.pushbullet.com/v2/pushes', headers=headers, json=data) def get_devices(self): if self.config['apikey']: @@ -2326,8 +2319,8 @@ class PUSHBULLET(Notifier): devices.update({'': ''}) return devices else: - logger.error(u"PlexPy Notifiers :: Unable to retrieve {name} devices list: " - "[{r.status_code}] {r.reason}: {r.text}".format(name=self.NAME, r=req)) + logger.error(u"PlexPy Notifiers :: Unable to retrieve {name} devices list: [{r.status_code}] {r.reason}".format(name=self.NAME, r=r)) + logger.debug(u"PlexPy Notifiers :: Request response: {}".format(request.server_message(r, True))) return {'': ''} else: @@ -2402,9 +2395,7 @@ class PUSHOVER(Notifier): headers = {'Content-type': 'application/x-www-form-urlencoded'} - r = requests.post('https://api.pushover.net/1/messages.json', headers=headers, data=data) - - return self.notify_success(req=r) + return self.make_request('https://api.pushover.net/1/messages.json', headers=headers, data=data) def get_sounds(self): if self.config['apitoken']: @@ -2418,8 +2409,8 @@ class PUSHOVER(Notifier): sounds.update({'': ''}) return sounds else: - logger.error(u"PlexPy Notifiers :: Unable to retrieve {name} sounds list: " - "[{r.status_code}] {r.reason}: {r.text}".format(name=self.NAME, r=req)) + logger.error(u"PlexPy Notifiers :: Unable to retrieve {name} sounds list: [{r.status_code}] {r.reason}".format(name=self.NAME, r=r)) + logger.debug(u"PlexPy Notifiers :: Request response: {}".format(request.server_message(r, True))) return {'': ''} else: @@ -2735,9 +2726,7 @@ class SLACK(Notifier): headers = {'Content-type': 'application/json'} - r = requests.post(self.config['hook'], headers=headers, json=data) - - return self.notify_success(req=r) + return self.make_request(self.config['hook'], headers=headers, json=data) def return_config_options(self): config_option = [{'label': 'Slack Webhook URL', @@ -2848,7 +2837,8 @@ class TELEGRAM(Notifier): if r.status_code == 200: logger.info(u"PlexPy Notifiers :: {name} poster sent.".format(name=self.NAME)) else: - logger.error(u"PlexPy Notifiers :: {name} poster failed: [{r.status_code}] {r.reason}: {r.text}".format(name=self.NAME, r=r)) + logger.error(u"PlexPy Notifiers :: {name} poster failed: [{r.status_code}] {r.reason}".format(name=self.NAME, r=r)) + logger.debug(u"PlexPy Notifiers :: Request response: {}".format(request.server_message(r, True))) data['text'] = text @@ -2860,9 +2850,7 @@ class TELEGRAM(Notifier): headers = {'Content-type': 'application/x-www-form-urlencoded'} - r = requests.post('https://api.telegram.org/bot{}/sendMessage'.format(self.config['bot_token']), headers=headers, data=data) - - return self.notify_success(req=r) + return self.make_request('https://api.telegram.org/bot{}/sendMessage'.format(self.config['bot_token']), headers=headers, data=data) def return_config_options(self): config_option = [{'label': 'Telegram Bot Token', diff --git a/plexpy/request.py b/plexpy/request.py index 33b15e97..cd527083 100644 --- a/plexpy/request.py +++ b/plexpy/request.py @@ -120,6 +120,88 @@ def request_response(url, method="get", auto_raise=True, logger.error("Request raised exception: %s", e) +def request_response2(url, method="get", auto_raise=True, + whitelist_status_code=None, lock=fake_lock, **kwargs): + """ + Convenient wrapper for `requests.get', which will capture the exceptions + and log them. On success, the Response object is returned. In case of a + exception, None is returned. + + Additionally, there is support for rate limiting. To use this feature, + supply a tuple of (lock, request_limit). The lock is used to make sure no + other request with the same lock is executed. The request limit is the + minimal time between two requests (and so 1/request_limit is the number of + requests per seconds). + """ + + # Convert whitelist_status_code to a list if needed + if whitelist_status_code and type(whitelist_status_code) != list: + whitelist_status_code = [whitelist_status_code] + + # Disable verification of SSL certificates if requested. Note: this could + # pose a security issue! + kwargs['verify'] = bool(plexpy.CONFIG.VERIFY_SSL_CERT) + + # Map method to the request.XXX method. This is a simple hack, but it + # allows requests to apply more magic per method. See lib/requests/api.py. + request_method = getattr(requests, method.lower()) + + response = None + err_msg = http_err = req_msg = None + + try: + with lock: + response = request_method(url, **kwargs) + + # If status code != OK, then raise exception, except if the status code + # is white listed. + if whitelist_status_code and auto_raise: + if response.status_code not in whitelist_status_code: + try: + response.raise_for_status() + except: + raise + elif auto_raise: + response.raise_for_status() + + except requests.exceptions.SSLError as e: + if kwargs["verify"]: + err_msg = "Unable to connect to remote host because of a SSL error." + else: + err_msg = "Unable to connect to remote host because of a SSL error, " \ + "with certificate verification turned off: {}".format(e) + + except requests.ConnectionError: + err_msg = "Unable to connect to remote host. Check if the remote host is up and running." + + except requests.Timeout: + err_msg = "Request to the remote host timed out." + + except requests.HTTPError as e: + if e.response is not None: + if e.response.status_code >= 500: + http_err = "[{e.response.status_code}] {e.response.reason} (remote server error).".format(e=e) + + elif e.response.status_code >= 400: + http_err = "[{e.response.status_code}] {e.response.reason} (local client error).".format(e=e) + + else: + http_err = "Unknown HTTP error." + + err_msg = "Request raised a HTTP error: {}".format(http_err) + + if plexpy.VERBOSE: + req_msg = server_message(e.response, return_msg=True) + + else: + err_msg = "Request raised a HTTP error: Unknown response." + + except requests.RequestException as e: + err_msg = "Request raised an exception: {}".format(e) + + return response, err_msg, req_msg + + def request_soup(url, **kwargs): """ Wrapper for `request_response', which will return a BeatifulSoup object if @@ -195,7 +277,7 @@ def request_feed(url, **kwargs): return feedparser.parse(response.content) -def server_message(response): +def server_message(response, return_msg=False): """ Extract server message from response and log in to logger with DEBUG level. @@ -235,4 +317,7 @@ def server_message(response): if len(message) > 150: message = message[:150] + "..." + if return_msg: + return message + logger.debug("Server responded with message: %s", message)