Merge branch 'master' of https://github.com/Javafant/diaspora-api
[diaspy.git] / diaspy / models.py
1 #!/usr/bin/env python3
2
3
4 import json
5 import re
6
7
8 """This module is only imported in other diaspy modules and
9 MUST NOT import anything.
10 """
11
12
13 class Aspect():
14 """This class represents an aspect.
15
16 Class can be initialized by passing either an id and/or name as
17 parameters.
18 If both are missing, an exception will be raised.
19 """
20 def __init__(self, connection, id=None, name=None):
21 self._connection = connection
22 self.id, self.name = id, name
23 if id and not name:
24 self.name = self._findname()
25 elif name and not id:
26 self.id = self._findid()
27 elif not id and not name:
28 raise Exception("Aspect must be initialized with either an id or name")
29
30 def _findname(self):
31 """Finds name for aspect.
32 """
33 name = None
34 aspects = self._connection.getUserInfo()['aspects']
35 for a in aspects:
36 if a['id'] == self.id:
37 name = a['name']
38 break
39 return name
40
41 def _findid(self):
42 """Finds id for aspect.
43 """
44 id = None
45 aspects = self._connection.getUserInfo()['aspects']
46 for a in aspects:
47 if a['name'] == self.name:
48 id = a['id']
49 break
50 return id
51
52 def getUsers(self):
53 """Returns list of GUIDs of users who are listed in this aspect.
54 """
55 start_regexp = re.compile('<ul +class=["\']contacts["\'] *>')
56 userline_regexp = re.compile('<a href=["\']/people/[a-z0-9]{16,16}["\']>[a-zA-Z0-9()@. _-]+</a>')
57 personid_regexp = 'alt=["\']{0}["\'] class=["\']avatar["\'] data-person_id=["\'][0-9]+["\']'
58 method_regexp = 'data-method="delete" data-person_id="{0}"'
59
60 ajax = self._connection.get('aspects/{0}/edit'.format(self.id)).text
61
62 #print(ajax)
63
64 begin = ajax.find(start_regexp.search(ajax).group(0))
65 #print(0)
66 end = ajax.find('</ul>')
67 ajax = ajax[begin:end]
68
69 #print(ajax)
70
71 usernames = [(line[17:33], line[35:-4]) for line in userline_regexp.findall(ajax)]
72 for guid, name in usernames:
73 print(name, guid)
74 personids = [re.compile(personid_regexp.format(name)).search(ajax).group(0) for guid, name in usernames]
75 for n, line in enumerate(personids):
76 i, id = -2, ''
77 while line[i].isdigit():
78 id = line[i] + id
79 i -= 1
80 personids[n] = (usernames[n][1], id)
81
82 users_in_aspect = []
83 for name, id in personids:
84 if re.compile(method_regexp.format(id, self.id)).search(ajax): users_in_aspect.append(name)
85
86 users = []
87 for i, user in enumerate(usernames):
88 guid, name = user
89 if name in users_in_aspect:
90 users.append(guid)
91 return users
92
93 def addUser(self, user_id):
94 """Add user to current aspect.
95
96 :param user_id: user to add to aspect
97 :type user: int
98 """
99 data = {'authenticity_token': self._connection.get_token(),
100 'aspect_id': self.id,
101 'person_id': user_id}
102
103 request = self._connection.post('aspect_memberships.json', data=data)
104
105 if request.status_code == 400:
106 raise Exception('duplicate record, user already exists in aspect: {0}'.format(request.status_code))
107 elif request.status_code == 404:
108 raise Exception('user not found from this pod: {0}'.format(request.status_code))
109 elif request.status_code != 200:
110 raise Exception('wrong status code: {0}'.format(request.status_code))
111 return request.json()
112
113 def removeUser(self, user_id):
114 """Remove user from current aspect.
115
116 :param user_id: user to remove from aspect
117 :type user: int
118 """
119 data = {'authenticity_token': self._connection.get_token(),
120 'aspect_id': self.id,
121 'person_id': user_id}
122
123 request = self.connection.delete('aspect_memberships/{0}.json'.format(self.id), data=data)
124
125 if request.status_code != 200:
126 raise Exception('wrong status code: {0}'.format(request.status_code))
127 return request.json()
128
129
130 class Notification():
131 """This class represents single notification.
132 """
133 _who_regexp = re.compile(r'/people/[0-9a-z]+" class=\'hovercardable')
134 _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')
135
136 def __init__(self, connection, data):
137 self._connection = connection
138 self.type = list(data.keys())[0]
139 self.data = data[self.type]
140 self.id = self.data['id']
141 self.unread = self.data['unread']
142
143 def __getitem__(self, key):
144 """Returns a key from notification data.
145 """
146 return self.data[key]
147
148 def __str__(self):
149 """Returns notification note.
150 """
151 string = re.sub('</?[a-z]+( *[a-z_-]+=["\'][\w():.,!?#/\- ]*["\'])* */?>', '', self.data['note_html'])
152 string = string.strip().split('\n')[0]
153 while ' ' in string: string = string.replace(' ', ' ')
154 return string
155
156 def __repr__(self):
157 """Returns notification note with more details.
158 """
159 return '{0}: {1}'.format(self.when(), str(self))
160
161 def who(self):
162 """Returns list of guids of the users who caused you to get the notification.
163 """
164 return [who[8:24] for who in self._who_regexp.findall(self.data['note_html'])]
165
166 def when(self):
167 """Returns UTC time as found in note_html.
168 """
169 return self._when_regexp.search(self.data['note_html']).group(0)
170
171 def mark(self, unread=False):
172 """Marks notification to read/unread.
173 Marks notification to read if `unread` is False.
174 Marks notification to unread if `unread` is True.
175
176 :param unread: which state set for notification
177 :type unread: bool
178 """
179 headers = {'x-csrf-token': self._connection.get_token()}
180 params = {'set_unread': json.dumps(unread)}
181 self._connection.put('notifications/{0}'.format(self['id']), params=params, headers=headers)
182 self.data['unread'] = unread
183
184
185 class Post():
186 """This class represents a post.
187
188 .. note::
189 Remember that you need to have access to the post.
190 """
191 def __init__(self, post_id, connection):
192 """
193 :param post_id: id or guid of the post
194 :type post_id: str
195 :param connection: connection object used to authenticate
196 :type connection: connection.Connection
197 """
198 self._connection = connection
199 self.post_id = post_id
200
201 def __repr__(self):
202 """Returns string containing more information then str().
203 """
204 data = self.get_data()
205 return '{0} ({1}): {2}'.format(data['author']['name'], data['author']['diaspora_id'], data['text'])
206
207 def __str__(self):
208 """Returns text of a post.
209 """
210 return self.get_data()['text']
211
212 def get_data(self):
213 """This function retrieves data of the post.
214 """
215 r = self._connection.get('posts/{0}.json'.format(self.post_id))
216 if r.status_code != 200:
217 raise Exception('wrong status code: {0}'.format(r.status_code))
218 return r.json()
219
220 def like(self):
221 """This function likes a post.
222 It abstracts the 'Like' functionality.
223
224 :returns: dict -- json formatted like object.
225 """
226 data = {'authenticity_token': self._connection.get_token()}
227
228 r = self._connection.post('posts/{0}/likes'.format(self.post_id),
229 data=data,
230 headers={'accept': 'application/json'})
231
232 if r.status_code != 201:
233 raise Exception('{0}: Post could not be liked.'
234 .format(r.status_code))
235
236 return r.json()
237
238 def delete_like(self):
239 """This function removes a like from a post
240 """
241 data = {'authenticity_token': self._connection.get_token()}
242
243 post_data = self.get_data()
244
245 r = self._connection.delete('posts/{0}/likes/{1}'
246 .format(self.post_id,
247 post_data['interactions']
248 ['likes'][0]['id']),
249 data=data)
250
251 if r.status_code != 204:
252 raise Exception('{0}: Like could not be removed.'
253 .format(r.status_code))
254
255 def reshare(self):
256 """This function reshares a post
257
258 """
259 post_data = self.get_data()
260
261 data = {'root_guid': post_data['guid'],
262 'authenticity_token': self._connection.get_token()}
263
264 r = self._connection.post('reshares',
265 data=data,
266 headers={'accept': 'application/json'})
267
268 if r.status_code != 201:
269 raise Exception('{0}: Post could not be reshared.'
270 .format(r.status_code))
271
272 return r.json()
273
274 def comment(self, text):
275 """This function comments on a post
276
277 :param text: text to comment.
278 :type text: str
279 """
280 data = {'text': text,
281 'authenticity_token': self._connection.get_token()}
282
283 r = self._connection.post('posts/{0}/comments'.format(self.post_id),
284 data=data,
285 headers={'accept': 'application/json'})
286
287 if r.status_code != 201:
288 raise Exception('{0}: Comment could not be posted.'
289 .format(r.status_code))
290
291 return r.json()
292
293 def delete_comment(self, comment_id):
294 """This function removes a comment from a post
295
296 :param comment_id: id of the comment to remove.
297 :type comment_id: str
298 """
299 data = {'authenticity_token': self._connection.get_token()}
300
301 r = self._connection.delete('posts/{0}/comments/{1}'
302 .format(self.post_id,
303 comment_id),
304 data=data,
305 headers={'accept': 'application/json'})
306
307 if r.status_code != 204:
308 raise Exception('{0}: Comment could not be deleted.'
309 .format(r.status_code))
310
311 def delete(self):
312 """ This function deletes this post
313 """
314 data = {'authenticity_token': self._connection.get_token()}
315 r = self._connection.delete('posts/{0}'.format(self.post_id),
316 data=data,
317 headers={'accept': 'application/json'})
318 if r.status_code != 204:
319 raise Exception('{0}: Post could not be deleted'.format(r.status_code))