Fetching profile information is possible
[diaspy.git] / diaspy / settings.py
CommitLineData
dea56a86
MM
1"""This module provides access to user's settings on Diaspora*.
2"""
3
4
fb680551 5import json
f61c14c1 6import os
fb680551
MM
7import re
8import urllib
f61c14c1 9import warnings
fb680551 10
f61c14c1 11from diaspy import errors, streams
0d5880af 12
fb680551 13
36f274c0
MM
14class Profile():
15 """Provides profile editing methods.
16 """
b74513c5
MM
17 firstname_regexp = re.compile('<input id="profile_first_name" name="profile\[first_name\]" type="text" value="(.*?)" />')
18 lastname_regexp = re.compile('<input id="profile_last_name" name="profile\[last_name\]" type="text" value="(.*?)" />')
19 bio_regexp = re.compile('<textarea id="profile_bio" name="profile\[bio\]" placeholder="Fill me out" rows="5">\n(.*?)</textarea>')
20 location_regexp = re.compile('<input id="profile_location" name="profile\[location\]" placeholder="Fill me out" type="text" value="(.*?)" />')
21 gender_regexp = re.compile('<input id="profile_gender" name="profile\[gender\]" placeholder="Fill me out" type="text" value="(.*?)" />')
22 birth_year_regexp = re.compile('<option selected="selected" value="([0-9]{4,4})">[0-9]{4,4}</option>')
23 birth_month_regexp = re.compile('<option selected="selected" value="([0-9]{1,2})">(.*?)</option>')
24 birth_day_regexp = re.compile('<option selected="selected" value="([0-9]{1,2})">[0-9]{1,2}</option>')
25 is_searchable_regexp = re.compile('<input checked="checked" id="profile_searchable" name="profile\[searchable\]" type="checkbox" value="(.*?)" />')
26 is_nsfw_regexp = re.compile('<input checked="checked" id="profile_nsfw" name="profile\[nsfw\]" type="checkbox" value="(.*?)" />')
27
36f274c0
MM
28 def __init__(self, connection):
29 self._connection = connection
30 self.data = {'utf-8': '✓',
31 '_method': 'put',
32 'profile[first_name]': '',
33 'profile[last_name]': '',
34 'profile[tag_string]': '',
35 'tags': '',
36 'file': '',
37 'profile[bio]': '',
38 'profile[location]': '',
39 'profile[gender]': '',
40 'profile[date][year]': '',
41 'profile[date][month]': '',
42 'profile[date][day]': '',
43 }
44
b74513c5
MM
45 def getName(self):
46 """Returns two-tuple: (first, last) name.
47 """
48 html = self._connection.get('profile/edit').text
49 first = self.firstname_regexp.search(html).group(1)
50 last = self.lastname_regexp.search(html).group(1)
51 return (first, last)
52
53 def getBio(self):
54 """Returns user bio.
55 """
56 html = self._connection.get('profile/edit').text
57 bio = self.bio_regexp.search(html).group(1)
58 return bio
59
60 def getLocation(self):
61 """Returns location string.
62 """
63 html = self._connection.get('profile/edit').text
64 location = self.location_regexp.search(html).group(1)
65 return location
66
67 def getGender(self):
68 """Returns location string.
69 """
70 html = self._connection.get('profile/edit').text
71 gender = self.gender_regexp.search(html).group(1)
72 return gender
73
74 def getBirthDate(self, named_month=False):
75 """Returns three-tuple: (year, month, day).
76
77 :param named_month: if True, return name of the month instead of integer
78 :type named_month: bool
79 """
80 html = self._connection.get('profile/edit').text
81 year = self.birth_year_regexp.search(html)
82 if year is None: year = -1
83 else: year = int(year.group(1))
84 month = self.birth_month_regexp.search(html)
85 if month is None:
86 if named_month: month = ''
87 else: month = -1
88 else:
89 if named_month:
90 month = month.group(2)
91 else:
92 month = int(month.group(1))
93 day = self.birth_day_regexp.search(html)
94 if day is None: day = -1
95 else: day = int(day.group(1))
96 return (year, month, day)
97
98 def isSearchable(self):
99 """Returns True if profile is searchable.
100 """
101 html = self._connection.get('profile/edit').text
102 searchable = self.is_searchable_regexp.search(html)
103 # this is because value="true" in every case so we just
104 # check if the field is "checked"
105 if searchable is None: searchable = False # if it isn't - the regexp just won't match
106 else: searchable = True
107 return searchable
108
109 def isNSFW(self):
110 """Returns True if profile is marked as NSFW.
111 """
112 html = self._connection.get('profile/edit').text
113 nsfw = self.is_nsfw_regexp.search(html)
114 if nsfw is None: nsfw = False
115 else: nsfw = True
116 return nsfw
117
36f274c0
MM
118 def setName(self, first='', last=''):
119 """Set first name.
120 """
121 data = self.data
122 data['profile[first_name]'] = first
123 data['profile[last_name]'] = last
124 data['authenticity_token'] = repr(self._connection)
36f274c0
MM
125 request = self._connection.post('profile', data=data, allow_redirects=False)
126 return request.status_code
127
128
b74513c5 129class Account():
fb680551
MM
130 """This object is used to get access to user's settings on
131 Diaspora* and provides interface for downloading user's stuff.
132 """
133 def __init__(self, connection):
134 self._connection = connection
135
136 def downloadxml(self):
f61c14c1
MM
137 """Returns downloaded XML.
138 """
fb680551
MM
139 request = self._connection.get('user/export')
140 return request.text
76745cf5 141
fe783229 142 def downloadPhotos(self, size='large', path='.', mark_nsfw=True, _critical=False, _stream=None):
f61c14c1
MM
143 """Downloads photos into the current working directory.
144 Sizes are: large, medium, small.
fe783229 145 Filename is: {post_guid}_{photo_guid}.{extension}
f61c14c1
MM
146
147 Normally, this method will catch urllib-generated errors and
148 just issue warnings about photos that couldn't be downloaded.
149 However, with _critical param set to True errors will become
150 critical - the will be reraised in finally block.
151
152 :param size: size of the photos to download - large, medium or small
153 :type size: str
154 :param path: path to download (defaults to current working directory
155 :type path: str
fe783229
MM
156 :param mark_nsfw: will append '-nsfw' to images from posts marked as nsfw,
157 :type mark_nsfw: bool
f61c14c1
MM
158 :param _stream: diaspy.streams.Generic-like object (only for testing)
159 :param _critical: if True urllib errors will be reraised after generating a warning (may be removed)
160
161 :returns: integer, number of photos downloaded
162 """
163 photos = 0
fe783229
MM
164 if _stream is None:
165 stream = streams.Activity(self._connection)
166 stream.full()
167 else:
168 stream = _stream
f61c14c1 169 for i, post in enumerate(stream):
fe783229
MM
170 if post['nsfw'] is not False: nsfw = '-nsfw'
171 else: nsfw = ''
f61c14c1
MM
172 if post['photos']:
173 for n, photo in enumerate(post['photos']):
fe783229 174 name = '{0}_{1}{2}.{3}'.format(post['guid'], photo['guid'], nsfw, photo['sizes'][size].split('.')[-1])
f61c14c1
MM
175 filename = os.path.join(path, name)
176 try:
177 urllib.request.urlretrieve(url=photo['sizes'][size], filename=filename)
178 except (urllib.error.HTTPError, urllib.error.URLError) as e:
179 warnings.warn('downloading image {0} from post {1}: {2}'.format(photo['guid'], post['guid'], e))
180 finally:
181 if _critical: raise
182 photos += 1
183 return photos
184
0d5880af 185 def setEmail(self, email):
76745cf5
MM
186 """Changes user's email.
187 """
36f274c0
MM
188 data = {'_method': 'put', 'utf8': '✓', 'user[email]': email, 'authenticity_token': repr(self._connection)}
189 request = self._connection.post('user', data=data, allow_redirects=False)
76745cf5 190
0d5880af
MM
191 def getEmail(self):
192 """Returns currently used email.
193 """
194 data = self._connection.get('user/edit')
195 email = re.compile('<input id="user_email" name="user\[email\]" size="30" type="text" value=".+?"').search(data.text)
196 if email is None: raise errors.DiaspyError('cannot fetch email')
197 email = email.group(0)[:-1]
198 email = email[email.rfind('"')+1:]
199 return email
200
201 def setLanguage(self, lang):
76745cf5 202 """Changes user's email.
52c871a4
MM
203
204 :param lang: language identifier from getLanguages()
76745cf5 205 """
36f274c0
MM
206 data = {'_method': 'put', 'utf8': '✓', 'user[language]': lang, 'authenticity_token': repr(self._connection)}
207 request = self._connection.post('user', data=data, allow_redirects=False)
208 return request.status_code
b7a15036
MM
209
210 def getLanguages(self):
211 """Returns a list of tuples containing ('Language name', 'identifier').
212 One of the Black Magic(tm) methods.
213 """
36f274c0
MM
214 selection_start = '<select id="user_language" name="user[language]">'
215 selection_end = '</select>'
b7a15036
MM
216 languages = []
217 request = self._connection.get('user/edit')
36f274c0
MM
218 data = request.text[request.text.find(selection_start)+len(selection_start):]
219 data = data[:data.find(selection_end)].split('\n')
dea56a86
MM
220 for item in data:
221 name = item[item.find('>')+1:item.rfind('<')]
222 identifier = item[item.find('"')+1:]
223 identifier = identifier[:identifier.find('"')]
224 languages.append((name, identifier))
b7a15036 225 return languages