Methods refactored to use _sessionpost(), tox.ini file added
[diaspy.git] / diaspy / client.py
1 import requests
2 import re
3 import json
4 import diaspy.models
5
6
7 class Client:
8 """This is the client class to connect to Diaspora.
9 """
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
18 """
19 self._token_regex = re.compile(r'content="(.*?)"\s+name="csrf-token')
20 self.pod = pod
21 self.session = requests.Session()
22 self._post_data = {}
23 self._setlogindata(username, password)
24 self._login()
25
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
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
60 def get_token(self):
61 """This function gets a token needed for authentication in most cases
62
63 :returns: string -- token used to authenticate
64 """
65 r = self._sessionget('stream')
66 token = self._token_regex.search(r.text).group(1)
67 return token
68
69 def _setlogindata(self, username, password):
70 """This function is used to set data for login.
71 .. note::
72 It should be called before _login() function.
73 """
74 self._username, self._password = username, password
75 self._login_data = {'user[username]': self._username,
76 'user[password]': self._password,
77 'authenticity_token': self.get_token()}
78
79 def _login(self):
80 """This function is used to connect to the pod and log in.
81 """
82 r = self._sessionpost('users/sign_in',
83 data=self._login_data,
84 headers={'accept': 'application/json'})
85 if r.status_code != 201:
86 raise Exception('{0}: Login failed.'.format(r.status_code))
87
88 def _setpostdata(self, text, aspect_ids, photos):
89 """This function prepares data for posting.
90
91 :param text: Text to post.
92 :type text: str
93 :param aspect_ids: Aspect ids to send post to.
94 :type aspect_ids: str
95 """
96 data = {}
97 data['aspect_ids'] = aspect_ids
98 data['status_message'] = {'text': text}
99 if photos:
100 data['photos'] = photos
101 self._post_data = data
102
103 def _post(self):
104 """Sends post to an aspect.
105
106 :returns: diaspy.models.Post -- the Post which has been created
107 """
108 r = self._sessionpost('status_messages',
109 data=json.dumps(self._post_data),
110 headers={'content-type': 'application/json',
111 'accept': 'application/json',
112 'x-csrf-token': self.get_token()})
113 if r.status_code != 201:
114 raise Exception('{0}: Post could not be posted.'.format(
115 r.status_code))
116
117 return diaspy.models.Post(str(r.json()['id']), self)
118
119 def post(self, text, aspect_ids='public', photos=None):
120 """This function sends a post to an aspect
121
122 :param text: Text to post.
123 :type text: str
124 :param aspect_ids: Aspect ids to send post to.
125 :type aspect_ids: str
126
127 :returns: diaspy.models.Post -- the Post which has been created
128 """
129 self._setpostdata(text, aspect_ids, photos)
130 post = self._post()
131 self._post_data = {}
132 return post
133
134 def get_user_info(self):
135 """This function returns the current user's attributes.
136
137 :returns: dict -- json formatted user info.
138 """
139 r = self._sessionget('bookmarklet')
140 regex = re.compile(r'window.current_user_attributes = ({.*})')
141 userdata = json.loads(regex.search(r.text).group(1))
142 return userdata
143
144 def post_picture(self, filename):
145 """This method posts a picture to D*.
146
147 :param filename: Path to picture file.
148 :type filename: str
149 """
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
164 r = self._sessionpost('photos', params=params, data=data, headers=headers)
165 return r
166
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.
171 """
172 r = self._sessionget('stream.json')
173
174 if r.status_code != 200:
175 raise Exception('wrong status code: {0}'.format(r.status_code))
176
177 stream = r.json()
178 return [diaspy.models.Post(str(post['id']), self) for post in stream]
179
180 def get_notifications(self):
181 """This functions returns a list of notifications.
182
183 :returns: list -- list of json formatted notifications
184 """
185 r = self._sessionget('notifications.json')
186
187 if r.status_code != 200:
188 raise Exception('wrong status code: {0}'.format(r.status_code))
189
190 notifications = r.json()
191 return notifications
192
193 def get_mentions(self):
194 """This functions returns a list of
195 posts the current user is being mentioned in.
196
197 :returns: list -- list of Post objects
198 """
199 r = self._sessionget('mentions.json')
200
201 if r.status_code != 200:
202 raise Exception('wrong status code: {0}'.format(r.status_code))
203
204 mentions = r.json()
205 return [diaspy.models.Post(str(post['id']), self) for post in mentions]
206
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
213 """
214 r = self._sessionget('tags/{0}.json'.format(tag))
215
216 if r.status_code != 200:
217 raise Exception('wrong status code: {0}'.format(r.status_code))
218
219 tagged_posts = r.json()
220 return [diaspy.models.Post(str(post['id']), self) for post in tagged_posts]
221
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()
233 return [diaspy.conversations.Conversation(str(conversation['conversation']['id']), self)
234 for conversation in mailbox]
235
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 """
245 data = {'authenticity_token': self.get_token(),
246 'aspect_id': aspect_id,
247 'person_id': user_id}
248
249 r = self._sessionpost('aspect_memberships.json', data=data)
250
251 if r.status_code != 201:
252 raise Exception('wrong status code: {0}'.format(r.status_code))
253 return r.json()
254
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
263 r = self._sessionpost('aspects', data=data)
264
265 if r.status_code != 200:
266 raise Exception('wrong status code: {0}'.format(r.status_code))
267
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
282 r = self.session.delete('{0}/aspect_memberships/42.json'.format(
283 self.pod),
284 data=data)
285
286 if r.status_code != 200:
287 raise Exception('wrong status code: {0}'.format(r.status_code))
288
289 return r.json()
290
291 def remove_aspect(self, aspect_id):
292 """ This function adds a new aspect.
293 """
294 data = {'authenticity_token': self.get_token()}
295
296 r = self.session.delete('{0}/aspects/{1}'.format(self.pod, aspect_id),
297 data=data)
298
299 if r.status_code != 404:
300 raise Exception('wrong status code: {0}'.format(r.status_code))
301
302 def new_conversation(self, contacts, subject, text):
303 """Start a new conversation.
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
311 """
312 data = {'contact_ids': contacts,
313 'conversation[subject]': subject,
314 'conversation[text]': text,
315 'utf8': '✓',
316 'authenticity_token': self.get_token()}
317
318 r = self._sessionpost('conversations/',
319 data=data,
320 headers={'accept': 'application/json'})
321 if r.status_code != 200:
322 raise Exception('{0}: Conversation could not be started.'
323 .format(r.status_code))
324 return r.json()