Commit | Line | Data |
---|---|---|
dea56a86 MM |
1 | """This module provides access to user's settings on Diaspora*. |
2 | """ | |
3 | ||
4 | ||
fb680551 | 5 | import json |
f61c14c1 | 6 | import os |
fb680551 MM |
7 | import re |
8 | import urllib | |
f61c14c1 | 9 | import warnings |
fb680551 | 10 | |
f61c14c1 | 11 | from diaspy import errors, streams |
0d5880af | 12 | |
fb680551 | 13 | |
36f274c0 MM |
14 | class 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 | 129 | class 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 |