Updated Makefile and one docstring in diaspy/people.py
[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):
60 return '{0} ({1})'.format(self['diaspora_name'], self['guid'])
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 """
f50cbea3 122 data = search.Search(self._connection).user(self['handle'])[0]
6d8d47ce 123 self.data = data
5131bd9e 124
39af9756
MM
125 def getHCard(self):
126 """Returns XML string containing user HCard.
127 """
128 request = self._connection.get('hcard/users/{0}'.format(self['guid']))
129 if request.status_code != 200: raise errors.UserError('could not fetch hcard for user: {0}'.format(self['guid']))
130 return request.text
131
dd0a4d9f 132
f8752360
MM
133class Me():
134 """Object represetnting current user.
135 """
136 _userinfo_regex = re.compile(r'window.current_user_attributes = ({.*})')
137 _userinfo_regex_2 = re.compile(r'gon.user=({.*});gon.preloads')
138
139 def __init__(self, connection):
140 self._connection = connection
141
39af9756 142 def getInfo(self):
f8752360
MM
143 """This function returns the current user's attributes.
144
1e578db9 145 :returns: dict
f8752360
MM
146 """
147 request = self._connection.get('bookmarklet')
148 userdata = self._userinfo_regex.search(request.text)
149 if userdata is None: userdata = self._userinfo_regex_2.search(request.text)
150 if userdata is None: raise errors.DiaspyError('cannot find user data')
151 userdata = userdata.group(1)
152 return json.loads(userdata)
153
154
dd0a4d9f
MM
155class Contacts():
156 """This class represents user's list of contacts.
157 """
158 def __init__(self, connection):
159 self._connection = connection
160
d589deff
MM
161 def add(self, user_id, aspect_ids):
162 """Add user to aspects of given ids.
dd0a4d9f 163
d589deff
MM
164 :param user_id: user guid
165 :type user_id: str
166 :param aspect_ids: list of aspect ids
167 :type aspect_ids: list
dd0a4d9f 168 """
7a818fdb 169 for aid in aspect_ids: Aspect(self._connection, aid).addUser(user_id)
27f09973 170
d589deff
MM
171 def remove(self, user_id, aspect_ids):
172 """Remove user from aspects of given ids.
27f09973 173
d589deff
MM
174 :param user_id: user guid
175 :type user_id: str
176 :param aspect_ids: list of aspect ids
177 :type aspect_ids: list
27f09973 178 """
7a818fdb 179 for aid in aspect_ids: Aspect(self._connection, aid).removeUser(user_id)
27f09973 180
d589deff 181 def get(self, set=''):
27f09973 182 """Returns list of user contacts.
d589deff
MM
183 Contact is a User() who is in one or more of user's
184 aspects.
185
7a818fdb
MM
186 By default, it will return list of users who are in
187 user's aspects.
188
d589deff
MM
189 If `set` is `all` it will also include users who only share
190 with logged user and are not in his/hers aspects.
7a818fdb 191
d589deff
MM
192 If `set` is `only_sharing` it will return users who are only
193 sharing with logged user and ARE NOT in his/hers aspects.
194
195 :param set: if passed could be 'all' or 'only_sharing'
196 :type set: str
27f09973 197 """
d589deff
MM
198 params = {}
199 if set: params['set'] = set
200
201 request = self._connection.get('contacts.json', params=params)
27f09973
MM
202 if request.status_code != 200:
203 raise Exception('status code {0}: cannot get contacts'.format(request.status_code))
f50cbea3 204 return [User(self._connection, guid=user['guid'], handle=user['handle'], fetch=None) for user in request.json()]