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 MM |
26 | def _sessionget(self, string): |
27 | """This method gets data from session. | |
28 | Performs additional checks if needed. | |
29 | ||
30 | Example: | |
31 | To obtain 'foo' from pod one should call `_sessionget('foo')`. | |
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. | |
40 | Performs additional checks if needed. | |
41 | ||
42 | Example: | |
43 | To post to 'foo' one should call `_sessionpost('foo', data={})`. | |
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 | |
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 MM |
69 | def _setlogindata(self, username, password): |
70 | """This function is used to set data for login. | |
94c2d637 | 71 | |
a7661afd | 72 | .. note:: |
03e37d6d | 73 | It should be called before _login() function. |
a993a4b6 | 74 | """ |
94c2d637 | 75 | self._username, self._password = username, password |
03e37d6d MM |
76 | self._login_data = { |
77 | 'user[username]': self._username, | |
78 | 'user[password]': self._password, | |
79 | 'authenticity_token': self.get_token(), | |
80 | } | |
a993a4b6 | 81 | |
03e37d6d MM |
82 | def _login(self): |
83 | """This function is used to connect to the pod and log in. | |
03e37d6d | 84 | """ |
d930e127 MM |
85 | r = self.session.post('{0}/users/sign_in'.format(self.pod), |
86 | data=self._login_data, | |
a7661afd | 87 | headers={'accept': 'application/json'}) |
d930e127 MM |
88 | |
89 | if r.status_code != 201: raise Exception('{0}: Login failed.'.format(r.status_code)) | |
313c9233 | 90 | |
a1a09a06 MM |
91 | def _setpostdata(self, text, aspect_id, photos): |
92 | """This function prepares data for posting. | |
93 | ||
313c9233 MM |
94 | :param text: Text to post. |
95 | :type text: str | |
96 | :param aspect_id: Aspect id to send post to. | |
97 | :type aspect_id: str | |
98 | """ | |
a1a09a06 MM |
99 | data = {} |
100 | data['aspect_id'] = aspect_id | |
101 | data['status_message'] = {'text': text} | |
313c9233 MM |
102 | if photos: data['photos'] = photos |
103 | self._post_data = data | |
104 | ||
a1a09a06 MM |
105 | def _post(self): |
106 | """Sends post to an aspect. | |
313c9233 | 107 | |
a1a09a06 | 108 | :returns: diaspy.models.Post -- the Post which has been created |
313c9233 | 109 | """ |
d930e127 | 110 | r = self.session.post('{0}/status_messages'.format(self.pod), |
a1a09a06 | 111 | data=json.dumps(self._post_data), |
28ca595a MK |
112 | headers={'content-type': 'application/json', |
113 | 'accept': 'application/json', | |
114 | 'x-csrf-token': self.get_token()}) | |
d930e127 | 115 | if r.status_code != 201: raise Exception('{0}: Post could not be posted.'.format(r.status_code)) |
313c9233 | 116 | |
95d2d310 | 117 | return diaspy.models.Post(str(r.json()['id']), self) |
313c9233 | 118 | |
28ca595a | 119 | def post(self, text, aspect_id='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 | |
124 | :param aspect_id: Aspect id to send post to. | |
125 | :type aspect_id: str | |
126 | ||
95d2d310 | 127 | :returns: diaspy.models.Post -- the Post which has been created |
a993a4b6 | 128 | """ |
a1a09a06 | 129 | self._setpostdata(text, aspect_id, 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 | ||
d930e127 | 164 | r = self.session.post('{0}/photos'.format(self.pod), |
4685fc31 | 165 | params=params, data=data, headers=headers) |
28ca595a MK |
166 | |
167 | return r | |
168 | ||
b356c9f9 MK |
169 | def get_stream(self): |
170 | """This functions returns a list of posts found in the stream. | |
171 | ||
172 | :returns: list -- list of Post objects. | |
b356c9f9 | 173 | """ |
af4eb01a | 174 | r = self._sessionget('stream.json') |
b356c9f9 | 175 | |
af4eb01a | 176 | if r.status_code != 200: |
d930e127 | 177 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
b356c9f9 MK |
178 | |
179 | stream = r.json() | |
af4eb01a | 180 | return [ diaspy.models.Post(str(post['id']), self) for post in stream ] |
33f21ecf MK |
181 | |
182 | def get_notifications(self): | |
183 | """This functions returns a list of notifications. | |
184 | ||
185 | :returns: list -- list of json formatted notifications | |
33f21ecf | 186 | """ |
af4eb01a | 187 | r = self._sessionget('notifications.json') |
33f21ecf | 188 | |
af4eb01a | 189 | if r.status_code != 200: |
d930e127 | 190 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
33f21ecf MK |
191 | |
192 | notifications = r.json() | |
193 | return notifications | |
3d3dff8f | 194 | |
3d3dff8f | 195 | def get_mentions(self): |
4685fc31 MK |
196 | """This functions returns a list of |
197 | posts the current user is being mentioned in. | |
3d3dff8f MK |
198 | |
199 | :returns: list -- list of Post objects | |
3d3dff8f | 200 | """ |
bebe44b5 | 201 | r = self._sessionget('mentions.json') |
3d3dff8f MK |
202 | |
203 | if r.status_code != 200: | |
d930e127 | 204 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
3d3dff8f MK |
205 | |
206 | mentions = r.json() | |
4f59eb52 | 207 | return [ diaspy.models.Post(str(post['id']), self) for post in mentions ] |
5c2b6162 | 208 | |
4685fc31 MK |
209 | def get_tag(self, tag): |
210 | """This functions returns a list of posts containing the tag. | |
211 | :param tag: Name of the tag | |
212 | :type tag: str | |
213 | ||
214 | :returns: list -- list of Post objects | |
4685fc31 | 215 | """ |
4f59eb52 | 216 | r = self._sessionget('tags/{0}.json'.format(tag)) |
4685fc31 MK |
217 | |
218 | if r.status_code != 200: | |
d930e127 | 219 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
4685fc31 MK |
220 | |
221 | tagged_posts = r.json() | |
4f59eb52 | 222 | return [ diaspy.models.Post(str(post['id']), self) for post in tagged_posts ] |
4685fc31 | 223 | |
264336e2 MM |
224 | def get_mailbox(self): |
225 | """This functions returns a list of messages found in the conversation. | |
226 | ||
227 | :returns: list -- list of Conversation objects. | |
228 | """ | |
229 | r = self._sessionget('conversations.json') | |
230 | ||
231 | if r.status_code != 200: | |
232 | raise Exception('wrong status code: {0}'.format(r.status_code)) | |
233 | ||
234 | mailbox = r.json() | |
235 | return [ diaspy.conversations.Conversation(str(conversation['conversation']['id']), self) for conversation in mailbox ] | |
236 | ||
5c2b6162 MK |
237 | def add_user_to_aspect(self, user_id, aspect_id): |
238 | """ this function adds a user to an aspect. | |
239 | ||
240 | :param user_id: User ID | |
241 | :type user_id: str | |
242 | :param aspect_id: Aspect ID | |
243 | :type aspect_id: str | |
244 | ||
245 | """ | |
246 | ||
247 | data = {'authenticity_token': self.get_token(), | |
248 | 'aspect_id': aspect_id, | |
249 | 'person_id': user_id} | |
250 | ||
d930e127 | 251 | r = self.session.post('{0}/aspect_memberships.json'.format(self.pod), |
5c2b6162 MK |
252 | data=data) |
253 | ||
254 | if r.status_code != 201: | |
d930e127 | 255 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
5c2b6162 MK |
256 | return r.json() |
257 | ||
e475a9d0 MM |
258 | def add_aspect(self, aspect_name, visible=0): |
259 | """ This function adds a new aspect. | |
260 | """ | |
261 | ||
262 | data = {'authenticity_token': self.get_token(), | |
263 | 'aspect[name]': aspect_name, | |
264 | 'aspect[contacts_visible]': visible} | |
265 | ||
266 | r = self.session.post('{0}/aspects'.format(self.pod), | |
267 | data=data) | |
268 | ||
269 | if r.status_code != 200: | |
270 | raise Exception('wrong status code: {0}'.format(r.status_code)) | |
271 | ||
5c2b6162 MK |
272 | def remove_user_from_aspect(self, user_id, aspect_id): |
273 | """ this function removes a user from an aspect. | |
274 | ||
275 | :param user_id: User ID | |
276 | :type user_id: str | |
277 | :param aspect_id: Aspect ID | |
278 | :type aspect_id: str | |
279 | ||
280 | """ | |
281 | ||
282 | data = {'authenticity_token': self.get_token(), | |
283 | 'aspect_id': aspect_id, | |
284 | 'person_id': user_id} | |
285 | ||
d930e127 | 286 | r = self.session.delete('{0}/aspect_memberships/42.json'.format(self.pod), |
5c2b6162 MK |
287 | data=data) |
288 | ||
289 | if r.status_code != 200: | |
d930e127 | 290 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
5c2b6162 MK |
291 | |
292 | return r.json() | |
22cb1646 | 293 | |
22cb1646 MK |
294 | def remove_aspect(self, aspect_id): |
295 | """ This function adds a new aspect. | |
296 | """ | |
22cb1646 MK |
297 | data = {'authenticity_token': self.get_token()} |
298 | ||
d930e127 | 299 | r = self.session.delete('{0}/aspects/{1}'.format(self.pod, aspect_id), |
4685fc31 | 300 | data=data) |
22cb1646 MK |
301 | |
302 | if r.status_code != 404: | |
d930e127 | 303 | raise Exception('wrong status code: {0}'.format(r.status_code)) |
91d2d5dc | 304 | |
91d2d5dc | 305 | def new_conversation(self, contacts, subject, text): |
264336e2 | 306 | """Start a new conversation. |
91d2d5dc B |
307 | |
308 | :param contacts: recipients ids, no guids, comma sperated. | |
309 | :type contacts: str | |
310 | :param subject: subject of the message. | |
311 | :type subject: str | |
312 | :param text: text of the message. | |
313 | :type text: str | |
91d2d5dc | 314 | """ |
91d2d5dc B |
315 | data = {'contact_ids': contacts, |
316 | 'conversation[subject]': subject, | |
317 | 'conversation[text]': text, | |
318 | 'utf8': '✓', | |
319 | 'authenticity_token': self.get_token()} | |
320 | ||
d930e127 MM |
321 | r = self.session.post('{0}/conversations/'.format(self.pod), |
322 | data=data, | |
323 | headers={'accept': 'application/json'}) | |
91d2d5dc | 324 | if r.status_code != 200: |
d930e127 | 325 | raise Exception('{0}: Conversation could not be started.'.format(r.status_code)) |
91d2d5dc B |
326 | |
327 | return r.json() |