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