Merge pull request #9 from marekjm/session-refactoring
[diaspy.git] / diaspy / client.py
CommitLineData
a993a4b6
MK
1import requests
2import re
3import json
95d2d310 4import diaspy.models
a993a4b6 5
4685fc31 6
a993a4b6 7class 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()