53d7ab5751198d54f651908226a3ecd5352f75ce
1 # -*- coding: utf-8 -*-
3 """This module provides access to user's settings on Diaspora*.
14 from bs4
import BeautifulSoup
17 print("[diaspy] BeautifulSoup not found, falling back on regex.")
18 else: BS4_SUPPORT
=True
20 from diaspy
import errors
, streams
24 """Provides interface to account settings.
27 email_regexp
= re
.compile('<input id="user_email" name="user\[email\]" size="30" type="text" value="(.+?)"')
28 language_option_regexp
= re
.compile('<option value="([_a-zA-Z-]+)"(?: selected="selected")?>(.*?)</option>')
30 def __init__(self
, connection
):
31 self
._connection
= connection
33 def downloadxml(self
):
34 """Returns downloaded XML.
36 request
= self
._connection
.get('user/export')
39 def downloadPhotos(self
, size
='large', path
='.', mark_nsfw
=True, _critical
=False, _stream
=None):
40 """Downloads photos into the current working directory.
41 Sizes are: large, medium, small.
42 Filename is: {post_guid}_{photo_guid}.{extension}
44 Normally, this method will catch urllib-generated errors and
45 just issue warnings about photos that couldn't be downloaded.
46 However, with _critical param set to True errors will become
47 critical - the will be reraised in finally block.
49 :param size: size of the photos to download - large, medium or small
51 :param path: path to download (defaults to current working directory
53 :param mark_nsfw: will append '-nsfw' to images from posts marked as nsfw,
55 :param _stream: diaspy.streams.Generic-like object (only for testing)
56 :param _critical: if True urllib errors will be reraised after generating a warning (may be removed)
58 :returns: integer, number of photos downloaded
62 stream
= streams
.Activity(self
._connection
)
66 for i
, post
in enumerate(stream
):
67 if post
['nsfw'] is not False: nsfw
= '-nsfw'
70 for n
, photo
in enumerate(post
['photos']):
71 # photo format -- .jpg, .png etc.
72 ext
= photo
['sizes'][size
].split('.')[-1]
73 name
= '{0}_{1}{2}.{3}'.format(post
['guid'], photo
['guid'], nsfw
, ext
)
74 filename
= os
.path
.join(path
, name
)
76 urllib
.request
.urlretrieve(url
=photo
['sizes'][size
], filename
=filename
)
77 except (urllib
.error
.HTTPError
, urllib
.error
.URLError
) as e
:
78 warnings
.warn('downloading image {0} from post {1}: {2}'.format(photo
['guid'], post
['guid'], e
))
84 def setEmail(self
, email
):
85 """Changes user's email.
87 data
= {'_method': 'put', 'utf8': '✓', 'user[email]': email
, 'authenticity_token': repr(self
._connection
)}
88 request
= self
._connection
.post('user', data
=data
, allow_redirects
=False)
89 if request
.status_code
!= 302:
90 raise errors
.SettingsError('setting email failed: {0}'.format(request
.status_code
))
93 """Returns currently used email.
95 data
= self
._connection
.get('user/edit')
97 soup
= BeautifulSoup(data
.text
, 'lxml')
98 email
= soup
.find('input', {"id": "user_email"})
99 if email
: email
= email
['value']
103 email
= self
.email_regexp
.search(data
.text
)
104 if email
is None: email
= ''
105 else: email
= email
.group(1)
108 def setLanguage(self
, lang
):
109 """Changes user's email.
111 :param lang: language identifier from getLanguages()
113 data
= {'_method': 'put', 'utf8': '✓', 'user[language]': lang
, 'authenticity_token': repr(self
._connection
)}
114 request
= self
._connection
.post('user', data
=data
, allow_redirects
=False)
115 if request
.status_code
!= 302:
116 raise errors
.SettingsError('setting language failed: {0}'.format(request
.status_code
))
118 def getLanguages(self
):
119 """Returns a list of tuples containing ('Language name', 'identifier').
120 One of the Black Magic(tm) methods.
122 request
= self
._connection
.get('user/edit')
124 soup
= BeautifulSoup(request
.text
, 'lxml')
125 language
= soup
.find('select', {"id": "user_language"})
126 return [(option
.text
, option
['value']) for option
in language
.findAll('option')]
128 return self
.language_option_regexp
.findall(request
.text
)
132 """Provides interface to provacy settings.
134 def __init__(self
, connection
):
135 self
._connection
= connection
139 """Provides interface to profile settigns.
143 Because of the way update requests for profile are created every field must be sent.
144 The `load()` method is used to load all information into the dictionary.
145 Setters can then be used to adjust the data.
146 Finally, `update()` can be called to send data back to pod.
149 firstname_regexp
= re
.compile('id="profile_first_name" name="profile\[first_name\]" type="text" value="(.*?)" />')
150 lastname_regexp
= re
.compile('id="profile_last_name" name="profile\[last_name\]" type="text" value="(.*?)" />')
151 bio_regexp
= re
.compile('<textarea id="profile_bio" name="profile\[bio\]" placeholder="Fill me out" rows="5">\n(.*?)</textarea>')
152 location_regexp
= re
.compile('id="profile_location" name="profile\[location\]" placeholder="Fill me out" type="text" value="(.*?)" />')
153 gender_regexp
= re
.compile('id="profile_gender" name="profile\[gender\]" placeholder="Fill me out" type="text" value="(.*?)" />')
154 birth_year_regexp
= re
.compile('selected="selected" value="([0-9]{4,4})">[0-9]{4,4}</option>')
155 birth_month_regexp
= re
.compile('selected="selected" value="([0-9]{1,2})">(.*?)</option>')
156 birth_day_regexp
= re
.compile('selected="selected" value="([0-9]{1,2})">[0-9]{1,2}</option>')
157 is_searchable_regexp
= re
.compile('checked="checked" id="profile_searchable" name="profile\[searchable\]" type="checkbox" value="(.*?)" />')
158 is_nsfw_regexp
= re
.compile('checked="checked" id="profile_nsfw" name="profile\[nsfw\]" type="checkbox" value="(.*?)" />')
160 def __init__(self
, connection
, no_load
=False):
161 self
._connection
= connection
162 self
.data
= {'utf-8': '✓',
164 'profile[first_name]': '',
165 'profile[last_name]': '',
166 'profile[tag_string]': '',
170 'profile[location]': '',
171 'profile[gender]': '',
172 'profile[date][year]': '',
173 'profile[date][month]': '',
174 'profile[date][day]': '',
176 self
._html
= self
._fetchhtml
()
178 if not no_load
: self
.load()
180 def _fetchhtml(self
):
181 """Fetches html that will be used to extract data.
183 return self
._connection
.get('profile/edit').text
186 """Returns two-tuple: (first, last) name.
189 soup
= BeautifulSoup(self
._html
, 'lxml')
190 first
= soup
.find('input', {"id": "profile_first_name"})
191 last
= soup
.find('input', {"id": "profile_last_name"})
192 return (first
['value'], last
['value'])
194 first
= self
.firstname_regexp
.search(self
._html
).group(1)
195 last
= self
.lastname_regexp
.search(self
._html
).group(1)
199 """Returns tags user had selected when describing him/her-self.
201 guid
= self
._connection
.getUserData()['guid']
202 html
= self
._connection
.get('people/{0}'.format(guid
)).text
204 soup
= BeautifulSoup(html
, 'lxml')
205 tags
= soup
.find('meta', {"name": "keywords"})
206 return [tag
.lower() for tag
in tags
['content'].split(", ")]
208 description_regexp
= re
.compile('<a href="/tags/(.*?)" class="tag">#.*?</a>')
209 return [tag
.lower() for tag
in re
.findall(description_regexp
, html
)]
215 soup
= BeautifulSoup(self
._html
, 'lxml')
216 bio
= soup
.find('textarea', {"id": "profile_bio"})
217 return bio
.get_text()
219 bio
= self
.bio_regexp
.search(self
._html
).group(1)
222 def getLocation(self
):
223 """Returns location string.
226 soup
= BeautifulSoup(self
._html
, 'lxml')
227 location
= soup
.find('input', {"id": "profile_location"})
228 return location
['value']
230 location
= self
.location_regexp
.search(self
._html
).group(1)
234 """Returns location string.
237 soup
= BeautifulSoup(self
._html
, 'lxml')
238 gender
= soup
.find('input', {"id": "profile_gender"})
239 return gender
['value']
241 gender
= self
.gender_regexp
.search(self
._html
).group(1)
244 def getBirthDate(self
, named_month
=False):
245 """Returns three-tuple: (year, month, day).
247 :param named_month: if True, return name of the month instead of integer
248 :type named_month: bool
251 soup
= BeautifulSoup(self
._html
, 'lxml')
253 year
= soup
.find('select', {"id": "profile_date_year"})
254 year_option
= year
.find('option', selected
=True)
255 if year_option
is None: year_option
= -1
256 else: year_option
= int(year_option
['value'])
258 month
= soup
.find('select', {"id": "profile_date_month"})
259 month_option
= month
.find('option', selected
=True)
260 if month_option
is None:
261 if named_month
: month_option
= ''
262 else: month_option
= -1
264 month_option
= month_option
.text
265 else: month_option
= month_option
['value']
267 day
= soup
.find('select', {"id": "profile_date_day"})
268 day_option
= day
.find('option', selected
=True)
269 if day_option
is None: day_option
= -1
270 else: day_option
= int(day_option
['value'])
271 return (year_option
, month_option
, day_option
)
273 year
= self
.birth_year_regexp
.search(self
._html
)
274 if year
is None: year
= -1
275 else: year
= int(year
.group(1))
276 month
= self
.birth_month_regexp
.search(self
._html
)
278 if named_month
: month
= ''
282 month
= month
.group(2)
284 month
= int(month
.group(1))
285 day
= self
.birth_day_regexp
.search(self
._html
)
286 if day
is None: day
= -1
287 else: day
= int(day
.group(1))
288 return (year
, month
, day
)
290 def isSearchable(self
):
291 """Returns True if profile is searchable.
294 soup
= BeautifulSoup(self
._html
, 'lxml')
295 searchable
= soup
.find('input', {"id": "profile_searchable"})
296 if (searchable
.has_attr('checked') and
297 searchable
['checked'] == 'checked'):
301 searchable
= self
.is_searchable_regexp
.search(self
._html
)
302 # this is because value="true" in every case so we just
303 # check if the field is "checked"
304 if searchable
is None: searchable
= False # if it isn't - the regexp just won't match
305 else: searchable
= True
309 """Returns True if profile is marked as NSFW.
312 soup
= BeautifulSoup(self
._html
, 'lxml')
313 nsfw
= soup
.find('input', {"id": "profile_nsfw"})
314 if (nsfw
.has_attr('checked') and
315 nsfw
['checked'] == 'checked'):
319 nsfw
= self
.is_nsfw_regexp
.search(self
._html
)
320 if nsfw
is None: nsfw
= False
324 def setName(self
, first
, last
):
325 """Set first and last name.
327 self
.data
['profile[first_name]'] = first
328 self
.data
['profile[last_name]'] = last
330 def setTags(self
, tags
):
331 """Sets tags that describe the user.
333 self
.data
['tags'] = ', '.join(['#{}'.format(tag
) for tag
in tags
])
335 def setBio(self
, bio
):
336 """Set bio of a user.
338 self
.data
['profile[bio]'] = bio
340 def setLocation(self
, location
):
341 """Set location of a user.
343 self
.data
['profile[location]'] = location
345 def setGender(self
, gender
):
346 """Set gender of a user.
348 self
.data
['profile[gender]'] = gender
350 def setBirthDate(self
, year
, month
, day
):
351 """Set birth date of a user.
353 self
.data
['profile[date][year]'] = year
354 self
.data
['profile[date][month]'] = month
355 self
.data
['profile[date][day]'] = day
357 def setSearchable(self
, searchable
):
358 """Set user's searchable status.
360 self
.data
['profile[searchable]'] = json
.dumps(searchable
)
362 def setNSFW(self
, nsfw
):
363 """Set user NSFW status.
365 self
.data
['profile[nsfw]'] = json
.dumps(nsfw
)
368 """Loads profile data into self.data dictionary.
369 **Notice:** Not all keys are loaded yet.
371 self
.setName(*self
.getName())
372 self
.setBio(self
.getBio())
373 self
.setLocation(self
.getLocation())
374 self
.setGender(self
.getGender())
375 self
.setBirthDate(*self
.getBirthDate(named_month
=False))
376 self
.setSearchable(self
.isSearchable())
377 self
.setNSFW(self
.isNSFW())
378 self
.setTags(self
.getTags())
382 """Updates profile information.
384 if not self
._loaded
: raise errors
.DiaspyError('profile was not loaded')
385 self
.data
['authenticity_token'] = repr(self
._connection
)
386 request
= self
._connection
.post('profile', data
=self
.data
, allow_redirects
=False)
387 return request
.status_code
391 """Provides interface to services settings.
393 def __init__(self
, connection
):
394 self
._connection
= connection