aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/index.rst23
-rw-r--r--mastodon/Mastodon.py4
-rw-r--r--mastodon/streaming.py60
3 files changed, 66 insertions, 21 deletions
diff --git a/docs/index.rst b/docs/index.rst
index 4d7073c..fc8c003 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -546,6 +546,26 @@ Push subscription dicts
546 # if webpushes have been requested for those events. 546 # if webpushes have been requested for those events.
547 } 547 }
548 548
549Push notification dicts
550~~~~~~~~~~~~~~~~~~~~~~~
551.. _push notification dict:
552
553.. code-block:: python
554
555 mastodon.push_subscription_decrypt_push(...)
556 # Returns the following dictionary
557 {
558 'access_token': # Access token that can be used to access the API as the
559 # notified user
560 'body': # Text body of the notification
561 'icon': # URL to an icon for the notification
562 'notification_id': # ID that can be passed to notification() to get the full
563 # notification object,
564 'notification_type': # 'mention', 'reblog', 'follow' or 'favourite'
565 'preferred_locale': # The users preferred locale
566 'title': # Title for the notification
567 }
568
549App registration and user authentication 569App registration and user authentication
550---------------------------------------- 570----------------------------------------
551Before you can use the mastodon API, you have to register your 571Before you can use the mastodon API, you have to register your
@@ -795,7 +815,8 @@ will return a handle corresponding to the open connection. If, in addition, `asy
795the thread will attempt to reconnect to the streaming API if any errors are encountered, waiting 815the thread will attempt to reconnect to the streaming API if any errors are encountered, waiting
796`async_reconnect_wait_sec` seconds between reconnection attempts. Note that no effort is made 816`async_reconnect_wait_sec` seconds between reconnection attempts. Note that no effort is made
797to "catch up" - events created while the connection is broken will not be received. If you need to make 817to "catch up" - events created while the connection is broken will not be received. If you need to make
798sure to get absolutely all notifications / deletes / toots, you will have to do that manually. 818sure to get absolutely all notifications / deletes / toots, you will have to do that manually, e.g.
819using the `on_abort` handler to fill in events since the last received one and then reconnecting.
799 820
800The connection may be closed at any time by calling the handles close() method. The 821The connection may be closed at any time by calling the handles close() method. The
801current status of the handler thread can be checked with the handles is_alive() function, 822current status of the handler thread can be checked with the handles is_alive() function,
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py
index c2da613..b5c82f8 100644
--- a/mastodon/Mastodon.py
+++ b/mastodon/Mastodon.py
@@ -132,6 +132,7 @@ class Mastodon:
132 __DICT_VERSION_ACTIVITY = "2.1.2" 132 __DICT_VERSION_ACTIVITY = "2.1.2"
133 __DICT_VERSION_REPORT = "1.1.0" 133 __DICT_VERSION_REPORT = "1.1.0"
134 __DICT_VERSION_PUSH = "2.4.0" 134 __DICT_VERSION_PUSH = "2.4.0"
135 __DICT_VERSION_PUSH_NOTIF = "2.4.0"
135 136
136 ### 137 ###
137 # Registering apps 138 # Registering apps
@@ -1609,13 +1610,14 @@ class Mastodon:
1609 1610
1610 return priv_dict, pub_dict 1611 return priv_dict, pub_dict
1611 1612
1613 @api_version("2.4.0", "2.4.0", __DICT_VERSION_PUSH_NOTIF)
1612 def push_subscription_decrypt_push(self, data, decrypt_params, encryption_header, crypto_key_header): 1614 def push_subscription_decrypt_push(self, data, decrypt_params, encryption_header, crypto_key_header):
1613 """ 1615 """
1614 Decrypts `data` received in a webpush request. Requires the private key dict 1616 Decrypts `data` received in a webpush request. Requires the private key dict
1615 from `push_subscription_generate_keys()`_ (`decrypt_params`) as well as the 1617 from `push_subscription_generate_keys()`_ (`decrypt_params`) as well as the
1616 Encryption and server Crypto-Key headers from the received webpush 1618 Encryption and server Crypto-Key headers from the received webpush
1617 1619
1618 Returns the decoded webpush. 1620 Returns the decoded webpush as a `push notification dict`_.
1619 """ 1621 """
1620 salt = self.__decode_webpush_b64(encryption_header.split("salt=")[1].strip()) 1622 salt = self.__decode_webpush_b64(encryption_header.split("salt=")[1].strip())
1621 dhparams = self.__decode_webpush_b64(crypto_key_header.split("dh=")[1].split(";")[0].strip()) 1623 dhparams = self.__decode_webpush_b64(crypto_key_header.split("dh=")[1].split(";")[0].strip())
diff --git a/mastodon/streaming.py b/mastodon/streaming.py
index 8c3ec19..65ec30a 100644
--- a/mastodon/streaming.py
+++ b/mastodon/streaming.py
@@ -25,8 +25,15 @@ class StreamListener(object):
25 describing the notification.""" 25 describing the notification."""
26 pass 26 pass
27 27
28 def on_abort(self): 28 def on_abort(self, err):
29 """There was a connection error or read timeout.""" 29 """There was a connection error, read timeout or other error fatal to
30 the streaming connection. The exception object about to be raised
31 is passed to this function for reference.
32
33 Note that the exception will be raised properly once you return from this
34 function, so if you are using this handler to reconnect, either never
35 return or start a thread and then catch and ignore the exception.
36 """
30 pass 37 pass
31 38
32 def on_delete(self, status_id): 39 def on_delete(self, status_id):
@@ -55,8 +62,10 @@ class StreamListener(object):
55 try: 62 try:
56 line = line_buffer.decode('utf-8') 63 line = line_buffer.decode('utf-8')
57 except UnicodeDecodeError as err: 64 except UnicodeDecodeError as err:
65 exception = MastodonMalformedEventError("Malformed UTF-8")
66 self.on_abort(exception)
58 six.raise_from( 67 six.raise_from(
59 MastodonMalformedEventError("Malformed UTF-8"), 68 exception,
60 err 69 err
61 ) 70 )
62 if line == '': 71 if line == '':
@@ -68,15 +77,17 @@ class StreamListener(object):
68 else: 77 else:
69 line_buffer.extend(chunk) 78 line_buffer.extend(chunk)
70 except ChunkedEncodingError as err: 79 except ChunkedEncodingError as err:
71 self.on_abort() 80 exception = MastodonNetworkError("Server ceased communication.")
81 self.on_abort(exception)
72 six.raise_from( 82 six.raise_from(
73 MastodonNetworkError("Server ceased communication."), 83 exception,
74 err 84 err
75 ) 85 )
76 except MastodonReadTimeout as err: 86 except MastodonReadTimeout as err:
77 self.on_abort() 87 exception = MastodonReadTimeout("Timed out while reading from server."),
88 self.on_abort(exception)
78 six.raise_from( 89 six.raise_from(
79 MastodonReadTimeout("Timed out while reading from server."), 90 exception,
80 err 91 err
81 ) 92 )
82 93
@@ -84,7 +95,12 @@ class StreamListener(object):
84 if line.startswith(':'): 95 if line.startswith(':'):
85 self.handle_heartbeat() 96 self.handle_heartbeat()
86 else: 97 else:
87 key, value = line.split(': ', 1) 98 try:
99 key, value = line.split(': ', 1)
100 except:
101 exception = MastodonMalformedEventError("Malformed event.")
102 self.on_abort(exception)
103 raise exception
88 # According to the MDN spec, repeating the 'data' key 104 # According to the MDN spec, repeating the 'data' key
89 # represents a newline(!) 105 # represents a newline(!)
90 if key in event: 106 if key in event:
@@ -99,24 +115,30 @@ class StreamListener(object):
99 data = event['data'] 115 data = event['data']
100 payload = json.loads(data, object_hook = Mastodon._Mastodon__json_hooks) 116 payload = json.loads(data, object_hook = Mastodon._Mastodon__json_hooks)
101 except KeyError as err: 117 except KeyError as err:
102 six.raise_from( 118 exception = MastodonMalformedEventError('Missing field', err.args[0], event)
103 MastodonMalformedEventError('Missing field', err.args[0], event), 119 self.on_abort(exception)
104 err 120 six.raise_from(
105 ) 121 exception,
122 err
123 )
106 except ValueError as err: 124 except ValueError as err:
107 # py2: plain ValueError 125 # py2: plain ValueError
108 # py3: json.JSONDecodeError, a subclass of ValueError 126 # py3: json.JSONDecodeError, a subclass of ValueError
109 six.raise_from( 127 exception = MastodonMalformedEventError('Bad JSON', data)
110 MastodonMalformedEventError('Bad JSON', data), 128 self.on_abort(exception)
111 err 129 six.raise_from(
112 ) 130 exception,
131 err
132 )
113 133
114 handler_name = 'on_' + name 134 handler_name = 'on_' + name
115 try: 135 try:
116 handler = getattr(self, handler_name) 136 handler = getattr(self, handler_name)
117 except AttributeError as err: 137 except AttributeError as err:
138 exception = MastodonMalformedEventError('Bad event type', name)
139 self.on_abort(exception)
118 six.raise_from( 140 six.raise_from(
119 MastodonMalformedEventError('Bad event type', name), 141 exception,
120 err 142 err
121 ) 143 )
122 else: 144 else:
Powered by cgit v1.2.3 (git 2.41.0)