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