4 """This module is only imported in other diaspy modules and
5 MUST NOT import anything.
12 from diaspy
import errors
16 """This class represents an aspect.
18 Class can be initialized by passing either an id and/or name as
20 If both are missing, an exception will be raised.
22 def __init__(self
, connection
, id=None, name
=None):
23 self
._connection
= connection
24 self
.id, self
.name
= id, name
26 self
.name
= self
._findname
()
28 self
.id = self
._findid
()
29 elif not id and not name
:
30 raise Exception('Aspect must be initialized with either an id or name')
33 """Finds name for aspect.
36 aspects
= self
._connection
.getUserInfo()['aspects']
38 if a
['id'] == self
.id:
44 """Finds id for aspect.
47 aspects
= self
._connection
.getUserInfo()['aspects']
49 if a
['name'] == self
.name
:
55 """Returns HTML returned when editing aspects via web UI.
57 start_regexp
= re
.compile('<ul +class=["\']contacts["\'] *>')
58 ajax
= self
._connection
.get('aspects/{0}/edit'.format(self
.id)).text
60 begin
= ajax
.find(start_regexp
.search(ajax
).group(0))
61 end
= ajax
.find('</ul>')
62 return ajax
[begin
:end
]
64 def _extractusernames(self
, ajax
):
65 """Extracts usernames and GUIDs from ajax returned by Diaspora*.
66 Returns list of two-tuples: (guid, diaspora_name).
68 userline_regexp
= re
.compile('<a href=["\']/people/[a-z0-9]{16,16}["\']>[\w()*@. -]+</a>')
69 return [(line
[17:33], re
.escape(line
[35:-4])) for line
in userline_regexp
.findall(ajax
)]
71 def _extractpersonids(self
, ajax
, usernames
):
72 """Extracts `person_id`s and usernames from ajax and list of usernames.
73 Returns list of two-tuples: (username, id)
75 personid_regexp
= 'alt=["\']{0}["\'] class=["\']avatar["\'] data-person_id=["\'][0-9]+["\']'
76 personids
= [re
.compile(personid_regexp
.format(name
)).search(ajax
).group(0) for guid
, name
in usernames
]
77 for n
, line
in enumerate(personids
):
79 while line
[i
].isdigit():
82 personids
[n
] = (usernames
[n
][1], id)
85 def _defineusers(self
, ajax
, personids
):
86 """Gets users contained in this aspect by getting users who have `delete` method.
88 method_regexp
= 'data-method="delete" data-person_id="{0}"'
90 for name
, id in personids
:
91 if re
.compile(method_regexp
.format(id)).search(ajax
): users
.append(name
)
94 def _getguids(self
, users_in_aspect
, usernames
):
95 """Defines users contained in this aspect.
98 for guid
, name
in usernames
:
99 if name
in users_in_aspect
: guids
.append(guid
)
103 """Returns list of GUIDs of users who are listed in this aspect.
105 ajax
= self
._getajax
()
106 usernames
= self
._extractusernames
(ajax
)
107 personids
= self
._extractpersonids
(ajax
, usernames
)
108 users_in_aspect
= self
._defineusers
(ajax
, personids
)
109 return self
._getguids
(users_in_aspect
, usernames
)
111 def addUser(self
, user_id
):
112 """Add user to current aspect.
114 :param user_id: user to add to aspect
116 :returns: JSON from request
118 data
= {'authenticity_token': repr(self
._connection
),
119 'aspect_id': self
.id,
120 'person_id': user_id
}
122 request
= self
._connection
.post('aspect_memberships.json', data
=data
)
124 if request
.status_code
== 400:
125 raise errors
.AspectError('duplicate record, user already exists in aspect: {0}'.format(request
.status_code
))
126 elif request
.status_code
== 404:
127 raise errors
.AspectError('user not found from this pod: {0}'.format(request
.status_code
))
128 elif request
.status_code
!= 200:
129 raise errors
.AspectError('wrong status code: {0}'.format(request
.status_code
))
130 return request
.json()
132 def removeUser(self
, user_id
):
133 """Remove user from current aspect.
135 :param user_id: user to remove from aspect
138 data
= {'authenticity_token': self
._connection
.get_token(),
139 'aspect_id': self
.id,
140 'person_id': user_id
}
141 request
= self
.connection
.delete('aspect_memberships/{0}.json'.format(self
.id), data
=data
)
143 if request
.status_code
!= 200:
144 raise errors
.AspectError('cannot remove user from aspect: {0}'.format(request
.status_code
))
145 return request
.json()
148 class Notification():
149 """This class represents single notification.
151 _who_regexp
= re
.compile(r
'/people/[0-9a-z]+" class=\'hovercardable
')
152 _when_regexp = re.compile(r'[0-9]{4,4}(-[0-9]{2,2}){2,2} [0-9]{2,2}(:[0-9]{2,2}){2,2} UTC
')
153 _aboutid_regexp = re.compile(r'/posts
/[0-9]+')
155 def __init__(self, connection, data):
156 self._connection = connection
157 self.type = list(data.keys())[0]
158 self.data = data[self.type]
159 self.id = self.data['id']
160 self.unread = self.data['unread
']
162 def __getitem__(self, key):
163 """Returns a key from notification data.
165 return self.data[key]
168 """Returns notification note.
170 string = re.sub('</?
[a
-z
]+( *[a
-z_
-]+=["\'][\w():.,!?#@=/\- ]*["\'])* */?
>', '', self.data['note_html
'])
171 string = string.strip().split('\n')[0]
172 while ' ' in string: string = string.replace(' ', ' ')
176 """Returns notification note with more details.
178 return '{0}
: {1}
'.format(self.when(), str(self))
181 """Returns id of post about which the notification is informing.
183 about = self._aboutid_regexp.search(self.data['note_html
'])
184 if about is None: about = self.who()
185 else: about = int(about.group(0)[7:])
189 """Returns list of guids of the users who caused you to get the notification.
191 return [who[8:24] for who in self._who_regexp.findall(self.data['note_html
'])]
194 """Returns UTC time as found in note_html.
196 return self._when_regexp.search(self.data['note_html
']).group(0)
198 def mark(self, unread=False):
199 """Marks notification to read/unread.
200 Marks notification to read if `unread` is False.
201 Marks notification to unread if `unread` is True.
203 :param unread: which state set for notification
206 headers = {'x
-csrf
-token
': repr(self._connection)}
207 params = {'set_unread
': json.dumps(unread)}
208 self._connection.put('notifications
/{0}
'.format(self['id']), params=params, headers=headers)
209 self.data['unread
'] = unread
212 class Conversation():
213 """This class represents a conversation.
216 Remember that you need to have access to the conversation.
218 def __init__(self, connection, id, fetch=True):
220 :param conv_id: id of the post and not the guid!
222 :param connection: connection object used to authenticate
223 :type connection: connection.Connection
225 self._connection = connection
228 if fetch: self._fetch()
231 """Fetches JSON data representing conversation.
233 request = self._connection.get('conversations
/{}.json
'.format(self.id))
234 if request.status_code == 200:
235 self.data = request.json()['conversation
']
237 raise errors.ConversationError('cannot download conversation data
: {0}
'.format(request.status_code))
239 def answer(self, text):
240 """Answer that conversation
242 :param text: text to answer.
245 data = {'message
[text
]': text,
247 'authenticity_token': repr(self
._connection
)}
249 request
= self
._connection
.post('conversations/{}/messages'.format(self
.id),
251 headers
={'accept': 'application/json'})
252 if request
.status_code
!= 200:
253 raise errors
.ConversationError('{0}: Answer could not be posted.'
254 .format(request
.status_code
))
255 return request
.json()
258 """Delete this conversation.
259 Has to be implemented.
261 data
= {'authenticity_token': repr(self
._connection
)}
263 request
= self
._connection
.delete('conversations/{0}/visibility/'
266 headers
={'accept': 'application/json'})
268 if request
.status_code
!= 404:
269 raise errors
.ConversationError('{0}: Conversation could not be deleted.'
270 .format(request
.status_code
))
272 def get_subject(self
):
273 """Returns the subject of this conversation
275 return self
.data
['subject']
279 """Represents comment on post.
281 Does not require Connection() object. Note that you should not manually
282 create `Comment()` objects -- they are designed to be created automatically
285 def __init__(self
, data
):
289 """Returns comment's text.
291 return self
.data
['text']
294 """Returns comments text and author.
295 Format: AUTHOR (AUTHOR'S GUID): COMMENT
297 return '{0} ({1}): {2}'.format(self
.author(), self
.author('guid'), str(self
))
300 """Returns time when the comment had been created.
302 return self
.data
['created_at']
304 def author(self
, key
='name'):
305 """Returns author of the comment.
307 return self
.data
['author'][key
]
311 """This class represents a post.
314 Remember that you need to have access to the post.
316 def __init__(self
, connection
, id, fetch
=True, comments
=True):
318 :param id: id or guid of the post
320 :param connection: connection object used to authenticate
321 :type connection: connection.Connection
322 :param fetch: defines whether to fetch post's data or not
324 :param comments: defines whether to fetch post's comments or not
327 self
._connection
= connection
331 if fetch
: self
._fetchdata
()
332 if comments
: self
._fetchcomments
()
335 """Returns string containing more information then str().
337 return '{0} ({1}): {2}'.format(self
.data
['author']['name'], self
.data
['author']['guid'], self
.data
['text'])
340 """Returns text of a post.
342 return self
.data
['text']
344 def _fetchdata(self
):
345 """This function retrieves data of the post.
347 request
= self
._connection
.get('posts/{0}.json'.format(self
.id))
348 if request
.status_code
!= 200:
349 raise errors
.PostError('{0}: could not fetch data for post: {1}'.format(request
.status_code
, self
.id))
351 self
.data
= request
.json()
353 def _fetchcomments(self
):
354 """Retireves comments for this post.
356 request
= self
._connection
.get('posts/{0}/comments.json'.format(self
.id))
357 if request
.status_code
!= 200:
358 raise errors
.PostError('{0}: could not fetch comments for post: {1}'.format(request
.status_code
, self
.id))
360 self
.comments
= [Comment(c
) for c
in request
.json()]
363 """Updates post data.
366 self
._fetchcomments
()
369 """This function likes a post.
370 It abstracts the 'Like' functionality.
372 :returns: dict -- json formatted like object.
374 data
= {'authenticity_token': repr(self
._connection
)}
376 request
= self
._connection
.post('posts/{0}/likes'.format(self
.id),
378 headers
={'accept': 'application/json'})
380 if request
.status_code
!= 201:
381 raise errors
.PostError('{0}: Post could not be liked.'
382 .format(request
.status_code
))
383 return request
.json()
386 """This function reshares a post
388 data
= {'root_guid': self
.data
['guid'],
389 'authenticity_token': repr(self
._connection
)}
391 request
= self
._connection
.post('reshares',
393 headers
={'accept': 'application/json'})
394 if request
.status_code
!= 201:
395 raise Exception('{0}: Post could not be reshared'.format(request
.status_code
))
396 return request
.json()
398 def comment(self
, text
):
399 """This function comments on a post
401 :param text: text to comment.
404 data
= {'text': text
,
405 'authenticity_token': repr(self
._connection
)}
406 request
= self
._connection
.post('posts/{0}/comments'.format(self
.id),
408 headers
={'accept': 'application/json'})
410 if request
.status_code
!= 201:
411 raise Exception('{0}: Comment could not be posted.'
412 .format(request
.status_code
))
413 return request
.json()
416 """ This function deletes this post
418 data
= {'authenticity_token': repr(self
._connection
)}
419 request
= self
._connection
.delete('posts/{0}'.format(self
.id),
421 headers
={'accept': 'application/json'})
422 if request
.status_code
!= 204:
423 raise errors
.PostError('{0}: Post could not be deleted'.format(request
.status_code
))
425 def delete_comment(self
, comment_id
):
426 """This function removes a comment from a post
428 :param comment_id: id of the comment to remove.
429 :type comment_id: str
431 data
= {'authenticity_token': repr(self
._connection
)}
432 request
= self
._connection
.delete('posts/{0}/comments/{1}'
436 headers
={'accept': 'application/json'})
438 if request
.status_code
!= 204:
439 raise errors
.PostError('{0}: Comment could not be deleted'
440 .format(request
.status_code
))
442 def delete_like(self
):
443 """This function removes a like from a post
445 data
= {'authenticity_token': self
._connection
.get_token()}
447 request
= self
._connection
.delete('posts/{0}/likes/{1}'
449 self
.data
['interactions']
452 if request
.status_code
!= 204:
453 raise errors
.PostError('{0}: Like could not be removed.'
454 .format(request
.status_code
))
456 def author(self
, key
='name'):
457 """Returns author of the post.
458 :param key: all keys available in data['author']
460 return self
.data
['author'][key
]