Merge branch 'master' into search
[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
488d7ff6 8class User():
beaa09fb
MM
9 """This class abstracts a D* user.
10 This object goes around the limitations of current D* API and will
11 extract user data using black magic.
12 However, no chickens are harmed when you use it.
17d8f406 13
7b99bf75
JR
14 The parameter fetch should be either 'posts', 'data' or 'none'. By
15 default it is 'posts' which means in addition to user data, stream
3cf4514e
MM
16 will be fetched. If user has not posted yet diaspy will not be able
17 to extract the information from his/her posts. Since there is no official
18 way to do it we rely on user posts. If this will be the case user
7b99bf75 19 will be notified with appropriate exception message.
3cf4514e 20
7b99bf75
JR
21 If fetch is 'data', only user data will be fetched. If the user is
22 not found, no exception will be returned.
23
24 When creating new User() one can pass either guid, handle and/or id as
25 optional parameters. GUID takes precedence over handle when fetching
26 user stream. When fetching user data, handle is required.
beaa09fb 27 """
7b99bf75 28 def __init__(self, connection, guid='', handle='', fetch='posts', id=0):
beaa09fb 29 self._connection = connection
6cd1bae0 30 self.stream = []
9aa1c960
MM
31 self.handle = handle
32 self.guid = guid
b9fb4030
JR
33 self.data = {
34 'guid': guid,
35 'handle': handle,
1e192d06 36 'id': id,
b9fb4030 37 }
9aa1c960 38 self._fetch(fetch)
beaa09fb
MM
39
40 def __getitem__(self, key):
41 return self.data[key]
42
1e192d06
MM
43 def __str__(self):
44 return self['guid']
45
46 def __repr__(self):
47 return '{0} ({1})'.format(self['diaspora_name'], self['guid'])
48
9aa1c960
MM
49 def _fetch(self, fetch):
50 """Fetch user posts or data.
51 """
b9fb4030 52 if fetch == 'posts':
9aa1c960
MM
53 if self.handle and not self.guid: self.fetchhandle()
54 else: self.fetchguid()
55 elif fetch == 'data' and self.handle:
1f779f83 56 self.fetchprofile()
b9fb4030 57
1f779f83 58 def _sephandle(self):
beaa09fb
MM
59 """Separate D* handle into pod pod and user.
60
beaa09fb
MM
61 :returns: two-tuple (pod, user)
62 """
9aa1c960
MM
63 if re.match('^[a-zA-Z]+[a-zA-Z0-9_-]*@[a-z0-9.]+\.[a-z]+$', self.handle) is None:
64 raise errors.UserError('invalid handle: {0}'.format(self.handle))
65 handle = self.handle.split('@')
beaa09fb
MM
66 pod, user = handle[1], handle[0]
67 return (pod, user)
3cf4514e 68
7c6fbe5b
MM
69 def _finalize_data(self, data):
70 names = [('id', 'id'),
71 ('handle', 'diaspora_id'),
72 ('guid', 'guid'),
73 ('name', 'diaspora_name'),
74 ('avatar', 'image_urls'),
75 ]
7b99bf75
JR
76 final = {}
77 for d, f in names:
78 final[f] = data[d]
79 return final
beaa09fb 80
a8fdc14a
MM
81 def _postproc(self, request):
82 """Makes necessary modifications to user data and
83 sets up a stream.
84
85 :param request: request object
86 :type request: request
beaa09fb 87 """
141216df 88 if request.status_code != 200:
a8fdc14a 89 raise Exception('wrong error code: {0}'.format(request.status_code))
141216df
MM
90 else:
91 request = request.json()
65b1f099 92 if not len(request): raise errors.UserError('cannot extract user data: no posts to analyze')
7c6fbe5b 93 self.data = self._finalize_data(request[0]['author'])
1f779f83 94 self.stream = Outer(self._connection, location='people/{0}.json'.format(self['guid']))
beaa09fb 95
1f779f83 96 def fetchhandle(self, protocol='https'):
7b99bf75 97 """Fetch user data and posts using Diaspora handle.
beaa09fb 98 """
1f779f83 99 pod, user = self._sephandle()
a8fdc14a
MM
100 request = self._connection.session.get('{0}://{1}/u/{2}.json'.format(protocol, pod, user))
101 self._postproc(request)
beaa09fb 102
1f779f83 103 def fetchguid(self):
7b99bf75 104 """Fetch user data and posts using guid.
beaa09fb 105 """
9aa1c960 106 request = self._connection.get('people/{0}.json'.format(self.guid))
17d8f406 107 self._postproc(request)
3cf4514e 108
dd0a4d9f
MM
109
110class Contacts():
111 """This class represents user's list of contacts.
112 """
113 def __init__(self, connection):
114 self._connection = connection
115
d589deff
MM
116 def add(self, user_id, aspect_ids):
117 """Add user to aspects of given ids.
dd0a4d9f 118
d589deff
MM
119 :param user_id: user guid
120 :type user_id: str
121 :param aspect_ids: list of aspect ids
122 :type aspect_ids: list
dd0a4d9f 123 """
7a818fdb 124 for aid in aspect_ids: Aspect(self._connection, aid).addUser(user_id)
27f09973 125
d589deff
MM
126 def remove(self, user_id, aspect_ids):
127 """Remove user from aspects of given ids.
27f09973 128
d589deff
MM
129 :param user_id: user guid
130 :type user_id: str
131 :param aspect_ids: list of aspect ids
132 :type aspect_ids: list
27f09973 133 """
7a818fdb 134 for aid in aspect_ids: Aspect(self._connection, aid).removeUser(user_id)
27f09973 135
d589deff 136 def get(self, set=''):
27f09973 137 """Returns list of user contacts.
d589deff
MM
138 Contact is a User() who is in one or more of user's
139 aspects.
140
7a818fdb
MM
141 By default, it will return list of users who are in
142 user's aspects.
143
d589deff
MM
144 If `set` is `all` it will also include users who only share
145 with logged user and are not in his/hers aspects.
7a818fdb 146
d589deff
MM
147 If `set` is `only_sharing` it will return users who are only
148 sharing with logged user and ARE NOT in his/hers aspects.
149
150 :param set: if passed could be 'all' or 'only_sharing'
151 :type set: str
27f09973 152 """
d589deff
MM
153 params = {}
154 if set: params['set'] = set
155
156 request = self._connection.get('contacts.json', params=params)
27f09973
MM
157 if request.status_code != 200:
158 raise Exception('status code {0}: cannot get contacts'.format(request.status_code))
ec27a0a1 159 contacts = [User(self._connection, guid=user['guid'], handle=user['handle'], fetch=None, id=user['id']) for user in request.json()]
27f09973 160 return contacts