f3d0048467e4a78f36cc9e4092fe3047e7c840df
[diaspy.git] / diaspy / people.py
1 import re
2 from diaspy.streams import Outer
3 from diaspy.models import Aspect
4 from diaspy import errors
5 from diaspy import search
6
7
8 class User():
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.
13
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
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
19 will be notified with appropriate exception message.
20
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.
27 """
28 def __init__(self, connection, guid='', handle='', fetch='posts', id=0):
29 self._connection = connection
30 self.stream = []
31 self.data = {
32 'guid': guid,
33 'handle': handle,
34 'id': id,
35 }
36 self._do_fetch(fetch)
37
38 def __getitem__(self, key):
39 return self.data[key]
40
41 def __str__(self):
42 return self['guid']
43
44 def __repr__(self):
45 return '{0} ({1})'.format(self['diaspora_name'], self['guid'])
46
47 def _do_fetch(self, fetch):
48 if fetch == 'posts':
49 if self['handle'] and self['guid']: self.fetchguid()
50 elif self['guid'] and not self['handle']: self.fetchguid()
51 elif self['handle'] and not self['guid']: self.fetchhandle()
52 elif fetch == 'data' and len(self['handle']):
53 self._postproc(search.Search(self._connection).users(query=handle)[0])
54
55 def _sephandle(self):
56 """Separate D* handle into pod pod and user.
57
58 :returns: two-tuple (pod, user)
59 """
60 if re.match('^[a-zA-Z]+[a-zA-Z0-9_-]*@[a-z0-9.]+\.[a-z]+$', self['handle']) is None:
61 raise Exception('invalid handle: {0}'.format(self['handle']))
62 handle = self['handle'].split('@')
63 pod, user = handle[1], handle[0]
64 return (pod, user)
65
66 def _finalize_data(self, data):
67 names = [('id', 'id'),
68 ('handle', 'diaspora_id'),
69 ('guid', 'guid'),
70 ('name', 'diaspora_name'),
71 ('avatar', 'image_urls'),
72 ]
73 final = {}
74 for d, f in names:
75 final[f] = data[d]
76 return final
77
78 def _postproc(self, request):
79 """Makes necessary modifications to user data and
80 sets up a stream.
81
82 :param request: request object
83 :type request: request
84 """
85 if request.status_code != 200:
86 raise Exception('wrong error code: {0}'.format(request.status_code))
87 else:
88 request = request.json()
89 if not len(request): raise errors.UserError('cannot extract user data: no posts to analyze')
90 self.data = self._finalize_data(request[0]['author'])
91 self.stream = Outer(self._connection, location='people/{0}.json'.format(self['guid']))
92
93 def fetchhandle(self, protocol='https'):
94 """Fetch user data and posts using Diaspora handle.
95 """
96 pod, user = self._sephandle()
97 request = self._connection.session.get('{0}://{1}/u/{2}.json'.format(protocol, pod, user))
98 self._postproc(request)
99
100 def fetchguid(self):
101 """Fetch user data and posts using guid.
102 """
103 request = self._connection.get('people/{0}.json'.format(self['guid']))
104 self._postproc(request)
105
106
107 class Contacts():
108 """This class represents user's list of contacts.
109 """
110 def __init__(self, connection):
111 self._connection = connection
112
113 def add(self, user_id, aspect_ids):
114 """Add user to aspects of given ids.
115
116 :param user_id: user guid
117 :type user_id: str
118 :param aspect_ids: list of aspect ids
119 :type aspect_ids: list
120 """
121 for aid in aspect_ids: Aspect(self._connection, aid).addUser(user_id)
122
123 def remove(self, user_id, aspect_ids):
124 """Remove user from aspects of given ids.
125
126 :param user_id: user guid
127 :type user_id: str
128 :param aspect_ids: list of aspect ids
129 :type aspect_ids: list
130 """
131 for aid in aspect_ids: Aspect(self._connection, aid).removeUser(user_id)
132
133 def get(self, set=''):
134 """Returns list of user contacts.
135 Contact is a User() who is in one or more of user's
136 aspects.
137
138 By default, it will return list of users who are in
139 user's aspects.
140
141 If `set` is `all` it will also include users who only share
142 with logged user and are not in his/hers aspects.
143
144 If `set` is `only_sharing` it will return users who are only
145 sharing with logged user and ARE NOT in his/hers aspects.
146
147 :param set: if passed could be 'all' or 'only_sharing'
148 :type set: str
149 """
150 params = {}
151 if set: params['set'] = set
152
153 request = self._connection.get('contacts.json', params=params)
154 if request.status_code != 200:
155 raise Exception('status code {0}: cannot get contacts'.format(request.status_code))
156 contacts = [User(self._connection, user['guid'], user['handle'], 'none', user['id']) for user in request.json()]
157 return contacts