diff options
author | Lorenz Diener <[email protected]> | 2016-11-25 20:57:53 +0100 |
---|---|---|
committer | Lorenz Diener <[email protected]> | 2016-11-25 20:57:53 +0100 |
commit | ab5889404144834b2671d2d25b207fe761418d5c (patch) | |
tree | 0bd05a5cd7d233b102cbf89176b88bf2af92ad5e | |
parent | e4e3a8eb93721bf12e4a5b2bf45dc3c2a473dc6f (diff) | |
parent | b958ce54ba32968ef159bda91c8f480c74374f68 (diff) | |
download | mastodon.py-ab5889404144834b2671d2d25b207fe761418d5c.tar.gz |
Merge remote-tracking branch 'refs/remotes/origin/master' into ratelimits
# Conflicts:
# mastodon/Mastodon.py
-rw-r--r-- | docs/index.rst | 117 | ||||
-rw-r--r-- | mastodon/Mastodon.py | 176 |
2 files changed, 213 insertions, 80 deletions
diff --git a/docs/index.rst b/docs/index.rst index a91cfb7..e7c9366 100644 --- a/docs/index.rst +++ b/docs/index.rst | |||
@@ -1,5 +1,7 @@ | |||
1 | Mastodon.py | 1 | Mastodon.py |
2 | =========== | 2 | =========== |
3 | .. py:module:: mastodon | ||
4 | .. py:class: Mastodon | ||
3 | 5 | ||
4 | .. code-block:: python | 6 | .. code-block:: python |
5 | 7 | ||
@@ -37,14 +39,114 @@ as a single python module. By default, it talks to the | |||
37 | `Mastodon flagship instance`_, but it can be set to talk to any | 39 | `Mastodon flagship instance`_, but it can be set to talk to any |
38 | node running Mastodon. | 40 | node running Mastodon. |
39 | 41 | ||
42 | A note about IDs | ||
43 | ---------------- | ||
44 | Mastodons API uses IDs in several places: User IDs, Toot IDs, ... | ||
45 | |||
46 | While debugging, it might be tempting to copy-paste in IDs from the | ||
47 | web interface into your code. This will not work, as the IDs on the web | ||
48 | interface and in the URLs are not the same as the IDs used internally | ||
49 | in the API, so don't do that. | ||
50 | |||
51 | Return values | ||
52 | ------------- | ||
40 | Unless otherwise specified, all data is returned as python | 53 | Unless otherwise specified, all data is returned as python |
41 | dictionaries, matching the JSON format used by the API. | 54 | dictionaries, matching the JSON format used by the API. |
42 | For complete documentation on what every function returns, | ||
43 | check the `Mastodon API docs`_, or just play around a bit - the | ||
44 | format of the data is generally very easy to understand. | ||
45 | 55 | ||
46 | .. py:module:: mastodon | 56 | User dicts |
47 | .. py:class: Mastodon | 57 | ~~~~~~~~~~ |
58 | .. code-block:: python | ||
59 | |||
60 | mastodon.account(<numerical id>) | ||
61 | # Returns the following dictionary: | ||
62 | { | ||
63 | 'display_name': The user's display name | ||
64 | 'acct': The user's account name as username@domain (@domain omitted for local users) | ||
65 | 'following_count': How many people they follow | ||
66 | 'url': Their URL; usually 'https://mastodon.social/users/<acct>' | ||
67 | 'statuses_count': How many statuses they have | ||
68 | 'followers_count': How many followers they have | ||
69 | 'avatar': URL for their avatar | ||
70 | 'note': Their bio | ||
71 | 'header': URL for their header image | ||
72 | 'id': Same as <numerical id> | ||
73 | 'username': The username (what you @ them with) | ||
74 | } | ||
75 | |||
76 | Toot dicts | ||
77 | ~~~~~~~~~~ | ||
78 | .. code-block:: python | ||
79 | |||
80 | mastodon.toot("Hello from Python") | ||
81 | # Returns the following dictionary: | ||
82 | { | ||
83 | 'sensitive': Denotes whether the toot is marked sensitive | ||
84 | 'created_at': Creation time | ||
85 | 'mentions': A list of account dicts mentioned in the toot | ||
86 | 'uri': Descriptor for the toot | ||
87 | EG 'tag:mastodon.social,2016-11-25:objectId=<id>:objectType=Status' | ||
88 | 'tags': A list of hashtag dicts used in the toot | ||
89 | 'in_reply_to_id': Numerical id of the toot this toot is in response to | ||
90 | 'id': Numerical id of this toot | ||
91 | 'reblogs_count': Number of reblogs | ||
92 | 'favourites_count': Number of favourites | ||
93 | 'reblog': Denotes whether the toot is a reblog | ||
94 | 'url': URL of the toot | ||
95 | 'content': Content of the toot, as HTML: '<p>Hello from Python</p>' | ||
96 | 'favourited': Denotes whether the logged in user has favourited this toot | ||
97 | 'account': Account dict for the logged in account | ||
98 | } | ||
99 | |||
100 | Relationship dicts | ||
101 | ~~~~~~~~~~~~~~~~~~ | ||
102 | .. code-block:: python | ||
103 | |||
104 | mastodon.account_follow(<numerical id>) | ||
105 | # Returns the following dictionary: | ||
106 | { | ||
107 | 'followed_by': Boolean denoting whether they follow you back | ||
108 | 'following': Boolean denoting whether you follow them | ||
109 | 'id': Numerical id (same one as <numerical id>) | ||
110 | 'blocking': Boolean denoting whether you are blocking them | ||
111 | } | ||
112 | |||
113 | Notification dicts | ||
114 | ~~~~~~~~~~~~~~~~~~ | ||
115 | .. code-block:: python | ||
116 | |||
117 | mastodon.notifications()[0] | ||
118 | # Returns the following dictionary: | ||
119 | { | ||
120 | 'id': id of the notification. | ||
121 | 'type': "mention", "reblog", "favourite" or "follow". | ||
122 | 'status': In case of "mention", the mentioning status. | ||
123 | In case of reblog / favourite, the reblogged / favourited status. | ||
124 | 'account': User dict of the user from whom the notification originates. | ||
125 | } | ||
126 | |||
127 | Context dicts | ||
128 | ~~~~~~~~~~~~~ | ||
129 | .. code-block:: python | ||
130 | |||
131 | mastodon.status_context(<numerical id>) | ||
132 | # Returns the following dictionary: | ||
133 | { | ||
134 | 'descendants': A list of toot dicts | ||
135 | 'ancestors': A list of toot dicts | ||
136 | } | ||
137 | |||
138 | Media dicts | ||
139 | ~~~~~~~~~~~ | ||
140 | .. code-block:: python | ||
141 | |||
142 | mastodon.media_post("image.jpg", "image/jpeg") | ||
143 | # Returns the following dictionary: | ||
144 | { | ||
145 | 'text_url': The display text for the media (what shows up in toots) | ||
146 | 'preview_url': The URL for the media preview | ||
147 | 'type': Media type, EG 'image' | ||
148 | 'url': The URL for the media | ||
149 | } | ||
48 | 150 | ||
49 | App registration and user authentication | 151 | App registration and user authentication |
50 | ---------------------------------------- | 152 | ---------------------------------------- |
@@ -91,7 +193,6 @@ This function allows you to get information about a users notifications. | |||
91 | 193 | ||
92 | .. automethod:: Mastodon.notifications | 194 | .. automethod:: Mastodon.notifications |
93 | 195 | ||
94 | |||
95 | Reading data: Accounts | 196 | Reading data: Accounts |
96 | ---------------------- | 197 | ---------------------- |
97 | These functions allow you to get information about accounts and | 198 | These functions allow you to get information about accounts and |
@@ -103,7 +204,6 @@ their relationships. | |||
103 | .. automethod:: Mastodon.account_following | 204 | .. automethod:: Mastodon.account_following |
104 | .. automethod:: Mastodon.account_followers | 205 | .. automethod:: Mastodon.account_followers |
105 | .. automethod:: Mastodon.account_relationships | 206 | .. automethod:: Mastodon.account_relationships |
106 | .. automethod:: Mastodon.account_suggestions | ||
107 | .. automethod:: Mastodon.account_search | 207 | .. automethod:: Mastodon.account_search |
108 | 208 | ||
109 | Writing data: Statuses | 209 | Writing data: Statuses |
@@ -113,11 +213,11 @@ interact with already posted statuses. | |||
113 | 213 | ||
114 | .. automethod:: Mastodon.status_post | 214 | .. automethod:: Mastodon.status_post |
115 | .. automethod:: Mastodon.toot | 215 | .. automethod:: Mastodon.toot |
116 | .. automethod:: Mastodon.status_delete | ||
117 | .. automethod:: Mastodon.status_reblog | 216 | .. automethod:: Mastodon.status_reblog |
118 | .. automethod:: Mastodon.status_unreblog | 217 | .. automethod:: Mastodon.status_unreblog |
119 | .. automethod:: Mastodon.status_favourite | 218 | .. automethod:: Mastodon.status_favourite |
120 | .. automethod:: Mastodon.status_unfavourite | 219 | .. automethod:: Mastodon.status_unfavourite |
220 | .. automethod:: Mastodon.status_delete | ||
121 | 221 | ||
122 | Writing data: Accounts | 222 | Writing data: Accounts |
123 | ---------------------- | 223 | ---------------------- |
@@ -137,6 +237,7 @@ to attach media to statuses. | |||
137 | 237 | ||
138 | .. automethod:: Mastodon.media_post | 238 | .. automethod:: Mastodon.media_post |
139 | 239 | ||
240 | |||
140 | .. _Mastodon: https://github.com/Gargron/mastodon | 241 | .. _Mastodon: https://github.com/Gargron/mastodon |
141 | .. _Mastodon flagship instance: http://mastodon.social/ | 242 | .. _Mastodon flagship instance: http://mastodon.social/ |
142 | .. _Mastodon api docs: https://github.com/Gargron/mastodon/wiki/API | 243 | .. _Mastodon api docs: https://github.com/Gargron/mastodon/wiki/API |
diff --git a/mastodon/Mastodon.py b/mastodon/Mastodon.py index 8af739d..bc1c52b 100644 --- a/mastodon/Mastodon.py +++ b/mastodon/Mastodon.py | |||
@@ -29,7 +29,7 @@ class Mastodon: | |||
29 | @staticmethod | 29 | @staticmethod |
30 | def create_app(client_name, scopes = ['read', 'write', 'follow'], redirect_uris = None, to_file = None, api_base_url = __DEFAULT_BASE_URL): | 30 | def create_app(client_name, scopes = ['read', 'write', 'follow'], redirect_uris = None, to_file = None, api_base_url = __DEFAULT_BASE_URL): |
31 | """ | 31 | """ |
32 | Creates a new app with given client_name and scopes (read, write, follow) | 32 | Create a new app with given client_name and scopes (read, write, follow) |
33 | 33 | ||
34 | Specify redirect_uris if you want users to be redirected to a certain page after authenticating. | 34 | Specify redirect_uris if you want users to be redirected to a certain page after authenticating. |
35 | Specify to_file to persist your apps info to a file so you can use them in the constructor. | 35 | Specify to_file to persist your apps info to a file so you can use them in the constructor. |
@@ -64,7 +64,7 @@ class Mastodon: | |||
64 | ### | 64 | ### |
65 | def __init__(self, client_id, client_secret = None, access_token = None, api_base_url = __DEFAULT_BASE_URL, debug_requests = False, ratelimit_method = "wait", ratelimit_pacefactor = 0.9): | 65 | def __init__(self, client_id, client_secret = None, access_token = None, api_base_url = __DEFAULT_BASE_URL, debug_requests = False, ratelimit_method = "wait", ratelimit_pacefactor = 0.9): |
66 | """ | 66 | """ |
67 | Creates a new API wrapper instance based on the given client_secret and client_id. If you | 67 | Create a new API wrapper instance based on the given client_secret and client_id. If you |
68 | give a client_id and it is not a file, you must also give a secret. | 68 | give a client_id and it is not a file, you must also give a secret. |
69 | 69 | ||
70 | You can also directly specify an access_token, directly or as a file. | 70 | You can also directly specify an access_token, directly or as a file. |
@@ -108,15 +108,15 @@ class Mastodon: | |||
108 | 108 | ||
109 | def log_in(self, username, password, scopes = ['read', 'write', 'follow'], to_file = None): | 109 | def log_in(self, username, password, scopes = ['read', 'write', 'follow'], to_file = None): |
110 | """ | 110 | """ |
111 | Logs in and sets access_token to what was returned. Note that your | 111 | Log in and sets access_token to what was returned. Note that your |
112 | username is the e-mail you use to log in into mastodon. | 112 | username is the e-mail you use to log in into mastodon. |
113 | 113 | ||
114 | Can persist access token to file, to be used in the constructor. | 114 | Can persist access token to file, to be used in the constructor. |
115 | 115 | ||
116 | Will throw an exception if username / password are wrong, scopes are not | 116 | Will throw a MastodonIllegalArgumentError if username / password |
117 | valid or granted scopes differ from requested. | 117 | are wrong, scopes are not valid or granted scopes differ from requested. |
118 | 118 | ||
119 | Returns the access_token, as well. | 119 | Returns the access_token. |
120 | """ | 120 | """ |
121 | params = self.__generate_params(locals()) | 121 | params = self.__generate_params(locals()) |
122 | params['client_id'] = self.client_id | 122 | params['client_id'] = self.client_id |
@@ -125,7 +125,7 @@ class Mastodon: | |||
125 | params['scope'] = " ".join(scopes) | 125 | params['scope'] = " ".join(scopes) |
126 | 126 | ||
127 | try: | 127 | try: |
128 | response = self.__api_request('POST', '/oauth/token', params) | 128 | response = self.__api_request('POST', '/oauth/token', params, do_ratelimiting = False) |
129 | self.access_token = response['access_token'] | 129 | self.access_token = response['access_token'] |
130 | except: | 130 | except: |
131 | raise MastodonIllegalArgumentError('Invalid user name, password or scopes.') | 131 | raise MastodonIllegalArgumentError('Invalid user name, password or scopes.') |
@@ -147,35 +147,45 @@ class Mastodon: | |||
147 | ## | 147 | ## |
148 | def timeline(self, timeline = "home", max_id = None, since_id = None, limit = None): | 148 | def timeline(self, timeline = "home", max_id = None, since_id = None, limit = None): |
149 | """ | 149 | """ |
150 | Returns statuses, most recent ones first. Timeline can be home, mentions, public | 150 | Fetch statuses, most recent ones first. Timeline can be home, mentions, public |
151 | or tag/hashtag. See the following functions documentation for what those do. | 151 | or tag/hashtag. See the following functions documentation for what those do. |
152 | 152 | ||
153 | The default timeline is the "home" timeline. | 153 | The default timeline is the "home" timeline. |
154 | |||
155 | Returns a list of toot dicts. | ||
154 | """ | 156 | """ |
155 | params = self.__generate_params(locals(), ['timeline']) | 157 | params = self.__generate_params(locals(), ['timeline']) |
156 | return self.__api_request('GET', '/api/v1/timelines/' + timeline, params) | 158 | return self.__api_request('GET', '/api/v1/timelines/' + timeline, params) |
157 | 159 | ||
158 | def timeline_home(self, max_id = None, since_id = None, limit = None): | 160 | def timeline_home(self, max_id = None, since_id = None, limit = None): |
159 | """ | 161 | """ |
160 | Returns the authenticated users home timeline (i.e. followed users and self). | 162 | Fetch the authenticated users home timeline (i.e. followed users and self). |
163 | |||
164 | Returns a list of toot dicts. | ||
161 | """ | 165 | """ |
162 | return self.timeline('home', max_id = max_id, since_id = since_id, limit = limit) | 166 | return self.timeline('home', max_id = max_id, since_id = since_id, limit = limit) |
163 | 167 | ||
164 | def timeline_mentions(self, max_id = None, since_id = None, limit = None): | 168 | def timeline_mentions(self, max_id = None, since_id = None, limit = None): |
165 | """ | 169 | """ |
166 | Returns the authenticated users mentions. | 170 | Fetches the authenticated users mentions. |
171 | |||
172 | Returns a list of toot dicts. | ||
167 | """ | 173 | """ |
168 | return self.timeline('mentions', max_id = max_id, since_id = since_id, limit = limit) | 174 | return self.timeline('mentions', max_id = max_id, since_id = since_id, limit = limit) |
169 | 175 | ||
170 | def timeline_public(self, max_id = None, since_id = None, limit = None): | 176 | def timeline_public(self, max_id = None, since_id = None, limit = None): |
171 | """ | 177 | """ |
172 | Returns the public / visible-network timeline. | 178 | Fetches the public / visible-network timeline. |
179 | |||
180 | Returns a list of toot dicts. | ||
173 | """ | 181 | """ |
174 | return self.timeline('public', max_id = max_id, since_id = since_id, limit = limit) | 182 | return self.timeline('public', max_id = max_id, since_id = since_id, limit = limit) |
175 | 183 | ||
176 | def timeline_hashtag(self, hashtag, max_id = None, since_id = None, limit = None): | 184 | def timeline_hashtag(self, hashtag, max_id = None, since_id = None, limit = None): |
177 | """ | 185 | """ |
178 | Returns all toots with a given hashtag. | 186 | Fetch a timeline of toots with a given hashtag. |
187 | |||
188 | Returns a list of toot dicts. | ||
179 | """ | 189 | """ |
180 | return self.timeline('tag/' + str(hashtag), max_id = max_id, since_id = since_id, limit = limit) | 190 | return self.timeline('tag/' + str(hashtag), max_id = max_id, since_id = since_id, limit = limit) |
181 | 191 | ||
@@ -184,25 +194,33 @@ class Mastodon: | |||
184 | ### | 194 | ### |
185 | def status(self, id): | 195 | def status(self, id): |
186 | """ | 196 | """ |
187 | Returns a status. | 197 | Fetch information about a single toot. |
198 | |||
199 | Returns a toot dict. | ||
188 | """ | 200 | """ |
189 | return self.__api_request('GET', '/api/v1/statuses/' + str(id)) | 201 | return self.__api_request('GET', '/api/v1/statuses/' + str(id)) |
190 | 202 | ||
191 | def status_context(self, id): | 203 | def status_context(self, id): |
192 | """ | 204 | """ |
193 | Returns ancestors and descendants of the status. | 205 | Fetch information about ancestors and descendants of a toot. |
206 | |||
207 | Returns a context dict. | ||
194 | """ | 208 | """ |
195 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/context') | 209 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/context') |
196 | 210 | ||
197 | def status_reblogged_by(self, id): | 211 | def status_reblogged_by(self, id): |
198 | """ | 212 | """ |
199 | Returns a list of users that have reblogged a status. | 213 | Fetch a list of users that have reblogged a status. |
214 | |||
215 | Returns a list of user dicts. | ||
200 | """ | 216 | """ |
201 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/reblogged_by') | 217 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/reblogged_by') |
202 | 218 | ||
203 | def status_favourited_by(self, id): | 219 | def status_favourited_by(self, id): |
204 | """ | 220 | """ |
205 | Returns a list of users that have favourited a status. | 221 | Fetch a list of users that have favourited a status. |
222 | |||
223 | Returns a list of user dicts. | ||
206 | """ | 224 | """ |
207 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/favourited_by') | 225 | return self.__api_request('GET', '/api/v1/statuses/' + str(id) + '/favourited_by') |
208 | 226 | ||
@@ -211,8 +229,10 @@ class Mastodon: | |||
211 | ### | 229 | ### |
212 | def notifications(self): | 230 | def notifications(self): |
213 | """ | 231 | """ |
214 | Returns notifications (mentions, favourites, reblogs, follows) for the authenticated | 232 | Fetch notifications (mentions, favourites, reblogs, follows) for the authenticated |
215 | user. | 233 | user. |
234 | |||
235 | Returns a list of notification dicts. | ||
216 | """ | 236 | """ |
217 | return self.__api_request('GET', '/api/v1/notifications') | 237 | return self.__api_request('GET', '/api/v1/notifications') |
218 | 238 | ||
@@ -221,53 +241,61 @@ class Mastodon: | |||
221 | ### | 241 | ### |
222 | def account(self, id): | 242 | def account(self, id): |
223 | """ | 243 | """ |
224 | Returns account. | 244 | Fetch account information by user id. |
245 | |||
246 | Returns a user dict. | ||
225 | """ | 247 | """ |
226 | return self.__api_request('GET', '/api/v1/accounts/' + str(id)) | 248 | return self.__api_request('GET', '/api/v1/accounts/' + str(id)) |
227 | 249 | ||
228 | def account_verify_credentials(self): | 250 | def account_verify_credentials(self): |
229 | """ | 251 | """ |
230 | Returns authenticated user's account. | 252 | Fetch authenticated user's account information. |
253 | |||
254 | Returns a user dict. | ||
231 | """ | 255 | """ |
232 | return self.__api_request('GET', '/api/v1/accounts/verify_credentials') | 256 | return self.__api_request('GET', '/api/v1/accounts/verify_credentials') |
233 | 257 | ||
234 | def account_statuses(self, id, max_id = None, since_id = None, limit = None): | 258 | def account_statuses(self, id, max_id = None, since_id = None, limit = None): |
235 | """ | 259 | """ |
236 | Returns statuses by user. Same options as timeline are permitted. | 260 | Fetch statuses by user id. Same options as timeline are permitted. |
261 | |||
262 | Returns a list of toot dicts. | ||
237 | """ | 263 | """ |
238 | params = self.__generate_params(locals(), ['id']) | 264 | params = self.__generate_params(locals(), ['id']) |
239 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/statuses', params) | 265 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/statuses', params) |
240 | 266 | ||
241 | def account_following(self, id): | 267 | def account_following(self, id): |
242 | """ | 268 | """ |
243 | Returns users the given user is following. | 269 | Fetch users the given user is following. |
270 | |||
271 | Returns a list of user dicts. | ||
244 | """ | 272 | """ |
245 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/following') | 273 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/following') |
246 | 274 | ||
247 | def account_followers(self, id): | 275 | def account_followers(self, id): |
248 | """ | 276 | """ |
249 | Returns users the given user is followed by. | 277 | Fetch users the given user is followed by. |
278 | |||
279 | Returns a list of user dicts. | ||
250 | """ | 280 | """ |
251 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/followers') | 281 | return self.__api_request('GET', '/api/v1/accounts/' + str(id) + '/followers') |
252 | 282 | ||
253 | def account_relationships(self, id): | 283 | def account_relationships(self, id): |
254 | """ | 284 | """ |
255 | Returns relationships (following, followed_by, blocking) of the logged in user to | 285 | Fetch relationships (following, followed_by, blocking) of the logged in user to |
256 | a given account. id can be a list. | 286 | a given account. id can be a list. |
287 | |||
288 | Returns a list of relationship dicts. | ||
257 | """ | 289 | """ |
258 | params = self.__generate_params(locals()) | 290 | params = self.__generate_params(locals()) |
259 | return self.__api_request('GET', '/api/v1/accounts/relationships', params) | 291 | return self.__api_request('GET', '/api/v1/accounts/relationships', params) |
260 | |||
261 | def account_suggestions(self): | ||
262 | """ | ||
263 | Returns accounts that the system suggests the authenticated user to follow. | ||
264 | """ | ||
265 | return self.__api_request('GET', '/api/v1/accounts/suggestions') | ||
266 | 292 | ||
267 | def account_search(self, q, limit = None): | 293 | def account_search(self, q, limit = None): |
268 | """ | 294 | """ |
269 | Returns matching accounts. Will lookup an account remotely if the search term is | 295 | Fetch matching accounts. Will lookup an account remotely if the search term is |
270 | in the username@domain format and not yet in the database. | 296 | in the username@domain format and not yet in the database. |
297 | |||
298 | Returns a list of user dicts. | ||
271 | """ | 299 | """ |
272 | params = self.__generate_params(locals()) | 300 | params = self.__generate_params(locals()) |
273 | return self.__api_request('GET', '/api/v1/accounts/search', params) | 301 | return self.__api_request('GET', '/api/v1/accounts/search', params) |
@@ -277,10 +305,10 @@ class Mastodon: | |||
277 | ### | 305 | ### |
278 | def status_post(self, status, in_reply_to_id = None, media_ids = None): | 306 | def status_post(self, status, in_reply_to_id = None, media_ids = None): |
279 | """ | 307 | """ |
280 | Posts a status. Can optionally be in reply to another status and contain | 308 | Post a status. Can optionally be in reply to another status and contain |
281 | up to four pieces of media (Uploaded via media_post()). | 309 | up to four pieces of media (Uploaded via media_post()). |
282 | 310 | ||
283 | Returns the new status. | 311 | Returns a toot dict with the new status. |
284 | """ | 312 | """ |
285 | params = self.__generate_params(locals()) | 313 | params = self.__generate_params(locals()) |
286 | return self.__api_request('POST', '/api/v1/statuses', params) | 314 | return self.__api_request('POST', '/api/v1/statuses', params) |
@@ -288,41 +316,46 @@ class Mastodon: | |||
288 | def toot(self, status): | 316 | def toot(self, status): |
289 | """ | 317 | """ |
290 | Synonym for status_post that only takes the status text as input. | 318 | Synonym for status_post that only takes the status text as input. |
319 | |||
320 | Returns a toot dict with the new status. | ||
291 | """ | 321 | """ |
292 | return self.status_post(status) | 322 | return self.status_post(status) |
293 | 323 | ||
294 | def status_delete(self, id): | 324 | def status_delete(self, id): |
295 | """ | 325 | """ |
296 | Deletes a status | 326 | Delete a status |
327 | |||
328 | Returns an empty dict for good measure. | ||
297 | """ | 329 | """ |
298 | return self.__api_request('DELETE', '/api/v1/statuses/' + str(id)) | 330 | return self.__api_request('DELETE', '/api/v1/statuses/' + str(id)) |
299 | 331 | ||
300 | def status_reblog(self, id): | 332 | def status_reblog(self, id): |
301 | """Reblogs a status. | 333 | """Reblog a status. |
302 | 334 | ||
303 | Returns a new status that wraps around the reblogged one.""" | 335 | Returns a toot with with a new status that wraps around the reblogged one. |
336 | """ | ||
304 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/reblog") | 337 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/reblog") |
305 | 338 | ||
306 | def status_unreblog(self, id): | 339 | def status_unreblog(self, id): |
307 | """ | 340 | """ |
308 | Un-reblogs a status. | 341 | Un-reblog a status. |
309 | 342 | ||
310 | Returns the status that used to be reblogged. | 343 | Returns a toot dict with the status that used to be reblogged. |
311 | """ | 344 | """ |
312 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unreblog") | 345 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unreblog") |
313 | 346 | ||
314 | def status_favourite(self, id): | 347 | def status_favourite(self, id): |
315 | """ | 348 | """ |
316 | Favourites a status. | 349 | Favourite a status. |
317 | 350 | ||
318 | Returns the favourited status. | 351 | Returns a toot dict with the favourited status. |
319 | """ | 352 | """ |
320 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/favourite") | 353 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/favourite") |
321 | 354 | ||
322 | def status_unfavourite(self, id): | 355 | def status_unfavourite(self, id): |
323 | """Favourites a status. | 356 | """Favourite a status. |
324 | 357 | ||
325 | Returns the un-favourited status. | 358 | Returns a toot dict with the un-favourited status. |
326 | """ | 359 | """ |
327 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unfavourite") | 360 | return self.__api_request('POST', '/api/v1/statuses/' + str(id) + "/unfavourite") |
328 | 361 | ||
@@ -331,33 +364,33 @@ class Mastodon: | |||
331 | ### | 364 | ### |
332 | def account_follow(self, id): | 365 | def account_follow(self, id): |
333 | """ | 366 | """ |
334 | Follows a user. | 367 | Follow a user. |
335 | 368 | ||
336 | Returns the updated relationship to the user. | 369 | Returns a relationship dict containing the updated relationship to the user. |
337 | """ | 370 | """ |
338 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/follow") | 371 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/follow") |
339 | 372 | ||
340 | def account_unfollow(self, id): | 373 | def account_unfollow(self, id): |
341 | """ | 374 | """ |
342 | Unfollows a user. | 375 | Unfollow a user. |
343 | 376 | ||
344 | Returns the updated relationship to the user. | 377 | Returns a relationship dict containing the updated relationship to the user. |
345 | """ | 378 | """ |
346 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unfollow") | 379 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unfollow") |
347 | 380 | ||
348 | def account_block(self, id): | 381 | def account_block(self, id): |
349 | """ | 382 | """ |
350 | Blocks a user. | 383 | Block a user. |
351 | 384 | ||
352 | Returns the updated relationship to the user. | 385 | Returns a relationship dict containing the updated relationship to the user. |
353 | """ | 386 | """ |
354 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/block") | 387 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/block") |
355 | 388 | ||
356 | def account_unblock(self, id): | 389 | def account_unblock(self, id): |
357 | """ | 390 | """ |
358 | Unblocks a user. | 391 | Unblock a user. |
359 | 392 | ||
360 | Returns the updated relationship to the user. | 393 | Returns a relationship dict containing the updated relationship to the user. |
361 | """ | 394 | """ |
362 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unblock") | 395 | return self.__api_request('POST', '/api/v1/accounts/' + str(id) + "/unblock") |
363 | 396 | ||
@@ -366,20 +399,18 @@ class Mastodon: | |||
366 | ### | 399 | ### |
367 | def media_post(self, media_file, mime_type = None): | 400 | def media_post(self, media_file, mime_type = None): |
368 | """ | 401 | """ |
369 | Posts an image. media_file can either be image data or | 402 | Post an image. media_file can either be image data or |
370 | a file name. If image data is passed directly, the mime | 403 | a file name. If image data is passed directly, the mime |
371 | type has to be specified manually, otherwise, it is | 404 | type has to be specified manually, otherwise, it is |
372 | determined from the file name. | 405 | determined from the file name. |
373 | 406 | ||
374 | Returns the uploaded media metadata object. Importantly, this contains | ||
375 | the ID that can then be used in status_post() to attach the media to | ||
376 | a toot. | ||
377 | |||
378 | Throws a MastodonIllegalArgumentError if the mime type of the | 407 | Throws a MastodonIllegalArgumentError if the mime type of the |
379 | passed data or file can not be determined properly. | 408 | passed data or file can not be determined properly. |
409 | |||
410 | Returns a media dict. This contains the id that can be used in | ||
411 | status_post to attach the media file to a toot. | ||
380 | """ | 412 | """ |
381 | 413 | if os.path.isfile(media_file) and mime_type == None: | |
382 | if os.path.isfile(media_file): | ||
383 | mime_type = mimetypes.guess_type(media_file)[0] | 414 | mime_type = mimetypes.guess_type(media_file)[0] |
384 | media_file = open(media_file, 'rb') | 415 | media_file = open(media_file, 'rb') |
385 | 416 | ||
@@ -395,7 +426,7 @@ class Mastodon: | |||
395 | ### | 426 | ### |
396 | # Internal helpers, dragons probably | 427 | # Internal helpers, dragons probably |
397 | ### | 428 | ### |
398 | def __api_request(self, method, endpoint, params = {}, files = {}): | 429 | def __api_request(self, method, endpoint, params = {}, files = {}, do_ratelimiting = True): |
399 | """ | 430 | """ |
400 | Internal API request helper. | 431 | Internal API request helper. |
401 | 432 | ||
@@ -410,7 +441,7 @@ class Mastodon: | |||
410 | 441 | ||
411 | # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it | 442 | # "pace" mode ratelimiting: Assume constant rate of requests, sleep a little less long than it |
412 | # would take to not hit the rate limit at that request rate. | 443 | # would take to not hit the rate limit at that request rate. |
413 | if self.ratelimit_method == "pace": | 444 | if do_ratelimiting and self.ratelimit_method == "pace": |
414 | if self.ratelimit_remaining == 0: | 445 | if self.ratelimit_remaining == 0: |
415 | to_next = self.ratelimit_reset - time.time() | 446 | to_next = self.ratelimit_reset - time.time() |
416 | if to_next > 0: | 447 | if to_next > 0: |
@@ -472,20 +503,21 @@ class Mastodon: | |||
472 | raise MastodonAPIError("Could not parse response as JSON, respose code was " + str(response_object.status_code)) | 503 | raise MastodonAPIError("Could not parse response as JSON, respose code was " + str(response_object.status_code)) |
473 | 504 | ||
474 | # Handle rate limiting | 505 | # Handle rate limiting |
475 | self.ratelimit_remaining = int(response_object.headers['X-RateLimit-Remaining']) | 506 | if 'X-RateLimit-Remaining' in response_object.headers and do_ratelimiting: |
476 | self.ratelimit_limit = int(response_object.headers['X-RateLimit-Limit']) | 507 | self.ratelimit_remaining = int(response_object.headers['X-RateLimit-Remaining']) |
477 | self.ratelimit_reset = (datetime.strptime(response_object.headers['X-RateLimit-Reset'], "%Y-%m-%dT%H:%M:%S.%fZ") - datetime(1970, 1, 1)).total_seconds() | 508 | self.ratelimit_limit = int(response_object.headers['X-RateLimit-Limit']) |
478 | self.ratelimit_lastcall = time.time() | 509 | self.ratelimit_reset = (datetime.strptime(response_object.headers['X-RateLimit-Reset'], "%Y-%m-%dT%H:%M:%S.%fZ") - datetime(1970, 1, 1)).total_seconds() |
479 | 510 | self.ratelimit_lastcall = time.time() | |
480 | if "error" in response and response["error"] == "Throttled": | 511 | |
481 | if self.ratelimit_method == "throw": | 512 | if "error" in response and response["error"] == "Throttled": |
482 | raise MastodonRatelimitError("Hit rate limit.") | 513 | if self.ratelimit_method == "throw": |
483 | 514 | raise MastodonRatelimitError("Hit rate limit.") | |
484 | if self.ratelimit_method == "wait" or self.ratelimit_method == "pace": | 515 | |
485 | to_next = self.ratelimit_reset - time.time() | 516 | if self.ratelimit_method == "wait" or self.ratelimit_method == "pace": |
486 | if to_next > 0: | 517 | to_next = self.ratelimit_reset - time.time() |
487 | time.sleep(to_next) | 518 | if to_next > 0: |
488 | request_complete = False | 519 | time.sleep(to_next) |
520 | request_complete = False | ||
489 | 521 | ||
490 | return response | 522 | return response |
491 | 523 | ||