Commit | Line | Data |
---|---|---|
a993a4b6 MK |
1 | import requests |
2 | import re | |
3 | import json | |
95d2d310 | 4 | import diaspy.models |
a993a4b6 | 5 | |
4685fc31 | 6 | |
a993a4b6 | 7 | class Client: |
d930e127 | 8 | """This is the client class to connect to Diaspora. |
a993a4b6 | 9 | """ |
a7661afd MK |
10 | def __init__(self, pod, username, password): |
11 | """ | |
12 | :param pod: The complete url of the diaspora pod to use. | |
13 | :type pod: str | |
14 | :param username: The username used to log in. | |
15 | :type username: str | |
16 | :param password: The password used to log in. | |
17 | :type password: str | |
a7661afd | 18 | """ |
a993a4b6 MK |
19 | self._token_regex = re.compile(r'content="(.*?)"\s+name="csrf-token') |
20 | self.pod = pod | |
21 | self.session = requests.Session() | |
313c9233 | 22 | self._post_data = {} |
94c2d637 | 23 | self._setlogindata(username, password) |
a1a09a06 | 24 | self._login() |
a993a4b6 | 25 | |
d88b0f94 | 26 | def _sessionget(self, string): |
88ec6cda MM |
27 | """This method gets data from session. |
28 | Performs additional checks if needed. | |
d88b0f94 MM |
29 | |
30 | Example: | |
88ec6cda | 31 | To obtain 'foo' from pod one should call `_sessionget('foo')`. |
d88b0f94 MM |
32 | |
33 | :param string: URL to get without the pod's URL and slash eg. 'stream'. | |
34 | :type string: str | |
35 | """ | |
36 | return self.session.get('{0}/{1}'.format(self.pod, string)) | |
37 | ||
e475a9d0 MM |
38 | def _sessionpost(self, string, data, headers={}, params={}): |
39 | """This method posts data to session. | |
88ec6cda | 40 | Performs additional checks if needed. |
e475a9d0 MM |
41 | |
42 | Example: | |
88ec6cda | 43 | To post to 'foo' one should call `_sessionpost('foo', data={})`. |
e475a9d0 MM |
44 | |
45 | :param string: URL to post without the pod's URL and slash eg. 'status_messages'. | |
46 | :type string: str | |
47 | :param data: Data to post. | |
48 | :param headers: Headers. | |
49 | :type headers: dict | |
50 | :param params: Optional parameters. | |
51 | :type params: dict | |
52 | """ | |
53 | string = '{0}/{1}'.format(self.pod, string) | |
54 | if headers and params: r = self.session.post(string, data=data, headers=headers, params=params) | |
55 | elif headers and not params: r = self.session.post(string, data=data, headers=headers) | |
56 | elif not headers and params: r = self.session.post(string, data=data, params=params) | |
57 | else: r = self.session.post(string, data=data) | |
58 | return r | |
88ec6cda | 59 | |
a993a4b6 | 60 | def get_token(self): |
ae221396 MK |
61 | """This function gets a token needed for authentication in most cases |
62 | ||
63 | :returns: string -- token used to authenticate | |
ae221396 | 64 | """ |
d88b0f94 | 65 | r = self._sessionget('stream') |
a993a4b6 MK |
66 | token = self._token_regex.search(r.text).group(1) |
67 | return token | |
68 | ||
03e37d6d | 69 | def _setlogindata(self, username, password): |
88ec6cda | 70 | """This function is used to set data for login. |
a7661afd | 71 | .. note:: |
03e37d6d | 72 | It should be called before _login() function. |
a993a4b6 | 73 | """ |
94c2d637 | 74 | self._username, self._password = username, password |
88ec6cda | 75 | self._login_data = {'user[username]': self._username, |
03e37d6d | 76 | 'user[password]': self._password, |
9088535d | 77 | 'authenticity_token': self.get_token()} |
a993a4b6 | 78 | |
03e37d6d MM |
79 | def _login(self): |
80 | """This function is used to connect to the pod and log in. | |
03e37d6d | 81 | """ |
490a69d0 | 82 | r = self._sessionpost('users/sign_in', |
d930e127 | 83 | data=self._login_data, |
a7661afd | 84 | headers={'accept': 'application/json'}) |
9088535d MK |
85 | if r.status_code != 201: |
86 | raise Exception('{0}: Login failed.'.format(r.status_code)) | |
313c9233 | 87 | |
9088535d | 88 | def _setpostdata(self, text, aspect_ids, photos): |
a1a09a06 | 89 | """This function prepares data for posting. |
9088535d | 90 | |
313c9233 MM |
91 | :param text: Text to post. |
92 | :type text: str | |
9088535d MK |
93 | :param aspect_ids: Aspect ids to send post to. |
94 | :type aspect_ids: str | |
313c9233 | 95 | """ |
a1a09a06 | 96 | data = {} |
9088535d | 97 | data['aspect_ids'] = aspect_ids |
a1a09a06 | 98 | data['status_message'] = {'text': text} |
9088535d MK |
99 | if photos: |
100 | data['photos'] = photos | |
313c9233 MM |
101 | self._post_data = data |
102 | ||
a1a09a06 MM |
103 | def _post(self): |
104 | """Sends post to an aspect. | |
313c9233 | 105 | |
a1a09a06 | 106 | :returns: diaspy.models.Post -- the Post which has been created |
313c9233 | 107 | """ |
490a69d0 | 108 | r = self._sessionpost('status_messages', |
a1a09a06 | 109 | data=json.dumps(self._post_data), |
28ca595a MK |
110 | headers={'content-type': 'application/json', |
111 | 'accept': 'application/json', | |
112 | 'x-csrf-token': self.get_token()}) | |
9088535d MK |
113 | if r.status_code != 201: |
114 | raise Exception('{0}: Post could not be posted.'.format( | |
115 | r.status_code)) | |
313c9233 | 116 | |
95d2d310 | 117 | return diaspy.models.Post(str(r.json()['id']), self) |
313c9233 | 118 | |
9088535d | 119 | def post(self, text, aspect_ids='public', photos=None): |
a993a4b6 MK |
120 | """This function sends a post to an aspect |
121 | ||
122 | :param text: Text to post. | |
123 | :type text: str | |
9088535d MK |
124 | :param aspect_ids: Aspect ids to send post to. |
125 | :type aspect_ids: str | |
a993a4b6 | 126 | |
95d2d310 | 127 | :returns: diaspy.models.Post -- the Post which has been created |
a993a4b6 | 128 | """ |
9088535d | 129 | self._setpostdata(text, aspect_ids, photos) |
9fae7c88 MM |
130 | post = self._post() |
131 | self._post_data = {} | |
132 | return post | |
a993a4b6 MK |
133 | |
134 | def get_user_info(self): | |
135 | """This function returns the current user's attributes. | |
136 | ||
137 | :returns: dict -- json formatted user info. | |
a993a4b6 | 138 | """ |
81367a60 | 139 | r = self._sessionget('bookmarklet') |
a993a4b6 MK |
140 | regex = re.compile(r'window.current_user_attributes = ({.*})') |
141 | userdata = json.loads(regex.search(r.text).group(1)) | |
142 | return userdata | |
b356c9f9 | 143 | |
28ca595a | 144 | def post_picture(self, filename): |
a1a09a06 MM |
145 | """This method posts a picture to D*. |
146 | ||
147 | :param filename: Path to picture file. | |
148 | :type filename: str | |
149 | """ | |
28ca595a MK |
150 | aspects = self.get_user_info()['aspects'] |
151 | params = {} | |
152 | params['photo[pending]'] = 'true' | |
153 | params['set_profile_image'] = '' | |
154 | params['qqfile'] = filename | |
155 | for i, aspect in enumerate(aspects): | |
156 | params['photo[aspect_ids][%d]' % (i)] = aspect['id'] | |
157 | ||
158 | data = open(filename, 'rb') | |
159 | ||
160 | headers = {'content-type': 'application/octet-stream', | |
161 | 'x-csrf-token': self.get_token(), | |
162 | 'x-file-name': filename} | |
163 | ||
490a69d0 | 164 | r = self._sessionpost('photos', params=params, data=data, headers=headers) |
28ca595a MK |
165 | return r |
166 | ||
b356c9f9 MK |
167 | def get_stream(self): |
168 | """This functions returns a list of posts found in the stream. | |
169 | ||
170 | :returns: list -- list of Post objects. | |
b356c9f9 | 171 | """ |
af4eb01a | 172 | r = self._sessionget('stream.json') |
b356c9f9 | 173 | |
88ec6cda | 174 | if r.status_code != 200: |
d930e127 | 175 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
b356c9f9 MK |
176 | |
177 | stream = r.json() | |
88ec6cda | 178 | return [diaspy.models.Post(str(post['id']), self) for post in stream] |
33f21ecf MK |
179 | |
180 | def get_notifications(self): | |
181 | """This functions returns a list of notifications. | |
182 | ||
183 | :returns: list -- list of json formatted notifications | |
33f21ecf | 184 | """ |
af4eb01a | 185 | r = self._sessionget('notifications.json') |
33f21ecf | 186 | |
88ec6cda | 187 | if r.status_code != 200: |
d930e127 | 188 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
33f21ecf MK |
189 | |
190 | notifications = r.json() | |
191 | return notifications | |
3d3dff8f | 192 | |
3d3dff8f | 193 | def get_mentions(self): |
4685fc31 MK |
194 | """This functions returns a list of |
195 | posts the current user is being mentioned in. | |
3d3dff8f MK |
196 | |
197 | :returns: list -- list of Post objects | |
3d3dff8f | 198 | """ |
bebe44b5 | 199 | r = self._sessionget('mentions.json') |
3d3dff8f MK |
200 | |
201 | if r.status_code != 200: | |
d930e127 | 202 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
3d3dff8f MK |
203 | |
204 | mentions = r.json() | |
88ec6cda | 205 | return [diaspy.models.Post(str(post['id']), self) for post in mentions] |
5c2b6162 | 206 | |
4685fc31 MK |
207 | def get_tag(self, tag): |
208 | """This functions returns a list of posts containing the tag. | |
209 | :param tag: Name of the tag | |
210 | :type tag: str | |
211 | ||
212 | :returns: list -- list of Post objects | |
4685fc31 | 213 | """ |
4f59eb52 | 214 | r = self._sessionget('tags/{0}.json'.format(tag)) |
4685fc31 MK |
215 | |
216 | if r.status_code != 200: | |
d930e127 | 217 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
4685fc31 MK |
218 | |
219 | tagged_posts = r.json() | |
88ec6cda | 220 | return [diaspy.models.Post(str(post['id']), self) for post in tagged_posts] |
4685fc31 | 221 | |
264336e2 MM |
222 | def get_mailbox(self): |
223 | """This functions returns a list of messages found in the conversation. | |
224 | ||
225 | :returns: list -- list of Conversation objects. | |
226 | """ | |
227 | r = self._sessionget('conversations.json') | |
228 | ||
229 | if r.status_code != 200: | |
230 | raise Exception('wrong status code: {0}'.format(r.status_code)) | |
231 | ||
232 | mailbox = r.json() | |
490a69d0 MM |
233 | return [diaspy.conversations.Conversation(str(conversation['conversation']['id']), self) |
234 | for conversation in mailbox] | |
264336e2 | 235 | |
5c2b6162 MK |
236 | def add_user_to_aspect(self, user_id, aspect_id): |
237 | """ this function adds a user to an aspect. | |
238 | ||
239 | :param user_id: User ID | |
240 | :type user_id: str | |
241 | :param aspect_id: Aspect ID | |
242 | :type aspect_id: str | |
243 | ||
244 | """ | |
5c2b6162 MK |
245 | data = {'authenticity_token': self.get_token(), |
246 | 'aspect_id': aspect_id, | |
247 | 'person_id': user_id} | |
248 | ||
490a69d0 | 249 | r = self._sessionpost('aspect_memberships.json', data=data) |
5c2b6162 MK |
250 | |
251 | if r.status_code != 201: | |
d930e127 | 252 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
5c2b6162 MK |
253 | return r.json() |
254 | ||
e475a9d0 MM |
255 | def add_aspect(self, aspect_name, visible=0): |
256 | """ This function adds a new aspect. | |
257 | """ | |
258 | ||
259 | data = {'authenticity_token': self.get_token(), | |
260 | 'aspect[name]': aspect_name, | |
261 | 'aspect[contacts_visible]': visible} | |
262 | ||
490a69d0 | 263 | r = self._sessionpost('aspects', data=data) |
e475a9d0 MM |
264 | |
265 | if r.status_code != 200: | |
266 | raise Exception('wrong status code: {0}'.format(r.status_code)) | |
267 | ||
5c2b6162 MK |
268 | def remove_user_from_aspect(self, user_id, aspect_id): |
269 | """ this function removes a user from an aspect. | |
270 | ||
271 | :param user_id: User ID | |
272 | :type user_id: str | |
273 | :param aspect_id: Aspect ID | |
274 | :type aspect_id: str | |
275 | ||
276 | """ | |
277 | ||
278 | data = {'authenticity_token': self.get_token(), | |
279 | 'aspect_id': aspect_id, | |
280 | 'person_id': user_id} | |
281 | ||
9088535d MK |
282 | r = self.session.delete('{0}/aspect_memberships/42.json'.format( |
283 | self.pod), | |
5c2b6162 MK |
284 | data=data) |
285 | ||
286 | if r.status_code != 200: | |
d930e127 | 287 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
5c2b6162 MK |
288 | |
289 | return r.json() | |
22cb1646 | 290 | |
22cb1646 MK |
291 | def remove_aspect(self, aspect_id): |
292 | """ This function adds a new aspect. | |
293 | """ | |
22cb1646 MK |
294 | data = {'authenticity_token': self.get_token()} |
295 | ||
d930e127 | 296 | r = self.session.delete('{0}/aspects/{1}'.format(self.pod, aspect_id), |
4685fc31 | 297 | data=data) |
22cb1646 MK |
298 | |
299 | if r.status_code != 404: | |
d930e127 | 300 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
91d2d5dc | 301 | |
91d2d5dc | 302 | def new_conversation(self, contacts, subject, text): |
264336e2 | 303 | """Start a new conversation. |
91d2d5dc B |
304 | |
305 | :param contacts: recipients ids, no guids, comma sperated. | |
306 | :type contacts: str | |
307 | :param subject: subject of the message. | |
308 | :type subject: str | |
309 | :param text: text of the message. | |
310 | :type text: str | |
91d2d5dc | 311 | """ |
91d2d5dc B |
312 | data = {'contact_ids': contacts, |
313 | 'conversation[subject]': subject, | |
314 | 'conversation[text]': text, | |
315 | 'utf8': '✓', | |
316 | 'authenticity_token': self.get_token()} | |
317 | ||
490a69d0 | 318 | r = self._sessionpost('conversations/', |
d930e127 MM |
319 | data=data, |
320 | headers={'accept': 'application/json'}) | |
91d2d5dc | 321 | if r.status_code != 200: |
9088535d MK |
322 | raise Exception('{0}: Conversation could not be started.' |
323 | .format(r.status_code)) | |
91d2d5dc | 324 | return r.json() |