Fixes #10
[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
MM
78 def __str__(self):
79 return self['guid']
80
81 def __repr__(self):
e21587b9 82 return '{0} ({1})'.format(self['handle'], self['guid'])
1e192d06 83
415337b6
MM
84 def _fetchstream(self):
85 self.stream = Outer(self._connection, location='people/{0}.json'.format(self['guid']))
86
9aa1c960
MM
87 def _fetch(self, fetch):
88 """Fetch user posts or data.
89 """
b9fb4030 90 if fetch == 'posts':
f50cbea3 91 if self['handle'] and not self['guid']: self.fetchhandle()
9aa1c960 92 else: self.fetchguid()
f50cbea3 93 elif fetch == 'data' and self['handle']:
1f779f83 94 self.fetchprofile()
b9fb4030 95
7c6fbe5b 96 def _finalize_data(self, data):
6d8d47ce 97 """Adjustments are needed to have similar results returned
f50cbea3 98 by search feature and fetchguid()/fetchhandle().
6d8d47ce 99 """
f50cbea3
MM
100 names = [('id', 'id'),
101 ('guid', 'guid'),
102 ('name', 'name'),
103 ('avatar', 'avatar'),
104 ('handle', 'diaspora_id'),
105 ]
7b99bf75 106 final = {}
6d8d47ce 107 for f, d in names:
7b99bf75
JR
108 final[f] = data[d]
109 return final
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))
f50cbea3 119 request = request.json()
65b1f099 120 if not len(request): raise errors.UserError('cannot extract user data: no posts to analyze')
7c6fbe5b 121 self.data = self._finalize_data(request[0]['author'])
beaa09fb 122
1f779f83 123 def fetchhandle(self, protocol='https'):
7b99bf75 124 """Fetch user data and posts using Diaspora handle.
beaa09fb 125 """
f50cbea3 126 pod, user = sephandle(self['handle'])
73a9e0d3 127 request = self._connection.get('{0}://{1}/u/{2}.json'.format(protocol, pod, user), direct=True)
a8fdc14a 128 self._postproc(request)
415337b6 129 self._fetchstream()
beaa09fb 130
1f779f83 131 def fetchguid(self):
7b99bf75 132 """Fetch user data and posts using guid.
beaa09fb 133 """
f50cbea3
MM
134 if self['guid']:
135 request = self._connection.get('people/{0}.json'.format(self['guid']))
3311aa0c 136 self._postproc(request)
415337b6 137 self._fetchstream()
3311aa0c
MM
138 else:
139 raise errors.UserError('GUID not set')
3cf4514e 140
5131bd9e
MM
141 def fetchprofile(self):
142 """Fetches user data.
143 """
03ffc9ea
MM
144 data = search.Search(self._connection).user(self['handle'])
145 if not data:
0cbdffd5 146 raise errors.UserError('user with handle "{0}" has not been found on pod "{1}"'.format(self['handle'], self._connection.pod))
03ffc9ea
MM
147 else:
148 self.data = data[0]
5131bd9e 149
39af9756
MM
150 def getHCard(self):
151 """Returns XML string containing user HCard.
152 """
153 request = self._connection.get('hcard/users/{0}'.format(self['guid']))
154 if request.status_code != 200: raise errors.UserError('could not fetch hcard for user: {0}'.format(self['guid']))
155 return request.text
156
dd0a4d9f 157
f8752360
MM
158class Me():
159 """Object represetnting current user.
160 """
161 _userinfo_regex = re.compile(r'window.current_user_attributes = ({.*})')
162 _userinfo_regex_2 = re.compile(r'gon.user=({.*});gon.preloads')
163
164 def __init__(self, connection):
165 self._connection = connection
166
39af9756 167 def getInfo(self):
f8752360
MM
168 """This function returns the current user's attributes.
169
1e578db9 170 :returns: dict
f8752360
MM
171 """
172 request = self._connection.get('bookmarklet')
173 userdata = self._userinfo_regex.search(request.text)
174 if userdata is None: userdata = self._userinfo_regex_2.search(request.text)
175 if userdata is None: raise errors.DiaspyError('cannot find user data')
176 userdata = userdata.group(1)
177 return json.loads(userdata)
178
179
dd0a4d9f
MM
180class Contacts():
181 """This class represents user's list of contacts.
182 """
183 def __init__(self, connection):
184 self._connection = connection
185
d589deff
MM
186 def add(self, user_id, aspect_ids):
187 """Add user to aspects of given ids.
dd0a4d9f 188
d589deff
MM
189 :param user_id: user guid
190 :type user_id: str
191 :param aspect_ids: list of aspect ids
192 :type aspect_ids: list
dd0a4d9f 193 """
7a818fdb 194 for aid in aspect_ids: Aspect(self._connection, aid).addUser(user_id)
27f09973 195
d589deff
MM
196 def remove(self, user_id, aspect_ids):
197 """Remove user from aspects of given ids.
27f09973 198
d589deff
MM
199 :param user_id: user guid
200 :type user_id: str
201 :param aspect_ids: list of aspect ids
202 :type aspect_ids: list
27f09973 203 """
7a818fdb 204 for aid in aspect_ids: Aspect(self._connection, aid).removeUser(user_id)
27f09973 205
d589deff 206 def get(self, set=''):
27f09973 207 """Returns list of user contacts.
d589deff
MM
208 Contact is a User() who is in one or more of user's
209 aspects.
210
7a818fdb
MM
211 By default, it will return list of users who are in
212 user's aspects.
213
d589deff
MM
214 If `set` is `all` it will also include users who only share
215 with logged user and are not in his/hers aspects.
7a818fdb 216
d589deff
MM
217 If `set` is `only_sharing` it will return users who are only
218 sharing with logged user and ARE NOT in his/hers aspects.
219
220 :param set: if passed could be 'all' or 'only_sharing'
221 :type set: str
27f09973 222 """
d589deff
MM
223 params = {}
224 if set: params['set'] = set
225
226 request = self._connection.get('contacts.json', params=params)
27f09973
MM
227 if request.status_code != 200:
228 raise Exception('status code {0}: cannot get contacts'.format(request.status_code))
d84905af 229 return [User.parse(self._connection, each) for each in request.json()]