Fixed bugs with getting, showing and deleting users. Deleting users still does not...
[diaspy.git] / diaspy / people.py
CommitLineData
74edacee
MM
1#!/usr/bin/env python3
2
3import json
beaa09fb
MM
4import re
5from diaspy.streams import Outer
d589deff 6from diaspy.models import Aspect
65b1f099 7from diaspy import errors
7c6fbe5b 8from diaspy import search
beaa09fb
MM
9
10
6d8d47ce
MM
11def sephandle(handle):
12 """Separate Diaspora* handle into pod pod and user.
13
14 :returns: two-tuple (pod, user)
15 """
16 if re.match('^[a-zA-Z]+[a-zA-Z0-9_-]*@[a-z0-9.]+\.[a-z]+$', handle) is None:
615edb73 17 raise errors.InvalidHandleError('{0}'.format(handle))
6d8d47ce
MM
18 handle = handle.split('@')
19 pod, user = handle[1], handle[0]
20 return (pod, user)
21
22
488d7ff6 23class User():
beaa09fb
MM
24 """This class abstracts a D* user.
25 This object goes around the limitations of current D* API and will
26 extract user data using black magic.
27 However, no chickens are harmed when you use it.
17d8f406 28
7b99bf75
JR
29 The parameter fetch should be either 'posts', 'data' or 'none'. By
30 default it is 'posts' which means in addition to user data, stream
3cf4514e
MM
31 will be fetched. If user has not posted yet diaspy will not be able
32 to extract the information from his/her posts. Since there is no official
33 way to do it we rely on user posts. If this will be the case user
7b99bf75 34 will be notified with appropriate exception message.
3cf4514e 35
7b99bf75
JR
36 If fetch is 'data', only user data will be fetched. If the user is
37 not found, no exception will be returned.
38
39 When creating new User() one can pass either guid, handle and/or id as
40 optional parameters. GUID takes precedence over handle when fetching
41 user stream. When fetching user data, handle is required.
beaa09fb 42 """
7b99bf75 43 def __init__(self, connection, guid='', handle='', fetch='posts', id=0):
beaa09fb 44 self._connection = connection
6cd1bae0 45 self.stream = []
b9fb4030
JR
46 self.data = {
47 'guid': guid,
48 'handle': handle,
1e192d06 49 'id': id,
b9fb4030 50 }
9aa1c960 51 self._fetch(fetch)
beaa09fb
MM
52
53 def __getitem__(self, key):
54 return self.data[key]
55
1e192d06
MM
56 def __str__(self):
57 return self['guid']
58
59 def __repr__(self):
e21587b9 60 return '{0} ({1})'.format(self['handle'], self['guid'])
1e192d06 61
415337b6
MM
62 def _fetchstream(self):
63 self.stream = Outer(self._connection, location='people/{0}.json'.format(self['guid']))
64
9aa1c960
MM
65 def _fetch(self, fetch):
66 """Fetch user posts or data.
67 """
b9fb4030 68 if fetch == 'posts':
f50cbea3 69 if self['handle'] and not self['guid']: self.fetchhandle()
9aa1c960 70 else: self.fetchguid()
f50cbea3 71 elif fetch == 'data' and self['handle']:
1f779f83 72 self.fetchprofile()
b9fb4030 73
7c6fbe5b 74 def _finalize_data(self, data):
6d8d47ce 75 """Adjustments are needed to have similar results returned
f50cbea3 76 by search feature and fetchguid()/fetchhandle().
6d8d47ce 77 """
f50cbea3
MM
78 names = [('id', 'id'),
79 ('guid', 'guid'),
80 ('name', 'name'),
81 ('avatar', 'avatar'),
82 ('handle', 'diaspora_id'),
83 ]
7b99bf75 84 final = {}
6d8d47ce 85 for f, d in names:
7b99bf75
JR
86 final[f] = data[d]
87 return final
beaa09fb 88
a8fdc14a
MM
89 def _postproc(self, request):
90 """Makes necessary modifications to user data and
91 sets up a stream.
92
93 :param request: request object
94 :type request: request
beaa09fb 95 """
6d8d47ce 96 if request.status_code != 200: raise Exception('wrong error code: {0}'.format(request.status_code))
f50cbea3 97 request = request.json()
65b1f099 98 if not len(request): raise errors.UserError('cannot extract user data: no posts to analyze')
7c6fbe5b 99 self.data = self._finalize_data(request[0]['author'])
beaa09fb 100
1f779f83 101 def fetchhandle(self, protocol='https'):
7b99bf75 102 """Fetch user data and posts using Diaspora handle.
beaa09fb 103 """
f50cbea3 104 pod, user = sephandle(self['handle'])
73a9e0d3 105 request = self._connection.get('{0}://{1}/u/{2}.json'.format(protocol, pod, user), direct=True)
a8fdc14a 106 self._postproc(request)
415337b6 107 self._fetchstream()
beaa09fb 108
1f779f83 109 def fetchguid(self):
7b99bf75 110 """Fetch user data and posts using guid.
beaa09fb 111 """
f50cbea3
MM
112 if self['guid']:
113 request = self._connection.get('people/{0}.json'.format(self['guid']))
3311aa0c 114 self._postproc(request)
415337b6 115 self._fetchstream()
3311aa0c
MM
116 else:
117 raise errors.UserError('GUID not set')
3cf4514e 118
5131bd9e
MM
119 def fetchprofile(self):
120 """Fetches user data.
121 """
e21587b9
F
122 result = search.Search(self._connection).user(self['handle'])
123
124 # Check if there were any results at all
125 if len(result) < 1:
126 raise errors.UserError('could not fetch profile of user: {0}'.format(self['handle']))
127
128 self.data = result[0]
5131bd9e 129
39af9756
MM
130 def getHCard(self):
131 """Returns XML string containing user HCard.
132 """
133 request = self._connection.get('hcard/users/{0}'.format(self['guid']))
134 if request.status_code != 200: raise errors.UserError('could not fetch hcard for user: {0}'.format(self['guid']))
135 return request.text
136
dd0a4d9f 137
f8752360
MM
138class Me():
139 """Object represetnting current user.
140 """
141 _userinfo_regex = re.compile(r'window.current_user_attributes = ({.*})')
142 _userinfo_regex_2 = re.compile(r'gon.user=({.*});gon.preloads')
143
144 def __init__(self, connection):
145 self._connection = connection
146
39af9756 147 def getInfo(self):
f8752360
MM
148 """This function returns the current user's attributes.
149
1e578db9 150 :returns: dict
f8752360
MM
151 """
152 request = self._connection.get('bookmarklet')
153 userdata = self._userinfo_regex.search(request.text)
154 if userdata is None: userdata = self._userinfo_regex_2.search(request.text)
155 if userdata is None: raise errors.DiaspyError('cannot find user data')
156 userdata = userdata.group(1)
157 return json.loads(userdata)
158
159
dd0a4d9f
MM
160class Contacts():
161 """This class represents user's list of contacts.
162 """
163 def __init__(self, connection):
164 self._connection = connection
165
d589deff
MM
166 def add(self, user_id, aspect_ids):
167 """Add user to aspects of given ids.
dd0a4d9f 168
d589deff
MM
169 :param user_id: user guid
170 :type user_id: str
171 :param aspect_ids: list of aspect ids
172 :type aspect_ids: list
dd0a4d9f 173 """
7a818fdb 174 for aid in aspect_ids: Aspect(self._connection, aid).addUser(user_id)
27f09973 175
d589deff
MM
176 def remove(self, user_id, aspect_ids):
177 """Remove user from aspects of given ids.
27f09973 178
d589deff
MM
179 :param user_id: user guid
180 :type user_id: str
181 :param aspect_ids: list of aspect ids
182 :type aspect_ids: list
27f09973 183 """
7a818fdb 184 for aid in aspect_ids: Aspect(self._connection, aid).removeUser(user_id)
27f09973 185
d589deff 186 def get(self, set=''):
27f09973 187 """Returns list of user contacts.
d589deff
MM
188 Contact is a User() who is in one or more of user's
189 aspects.
190
7a818fdb
MM
191 By default, it will return list of users who are in
192 user's aspects.
193
d589deff
MM
194 If `set` is `all` it will also include users who only share
195 with logged user and are not in his/hers aspects.
7a818fdb 196
d589deff
MM
197 If `set` is `only_sharing` it will return users who are only
198 sharing with logged user and ARE NOT in his/hers aspects.
199
200 :param set: if passed could be 'all' or 'only_sharing'
201 :type set: str
27f09973 202 """
d589deff
MM
203 params = {}
204 if set: params['set'] = set
205
206 request = self._connection.get('contacts.json', params=params)
27f09973
MM
207 if request.status_code != 200:
208 raise Exception('status code {0}: cannot get contacts'.format(request.status_code))
f50cbea3 209 return [User(self._connection, guid=user['guid'], handle=user['handle'], fetch=None) for user in request.json()]