# HG changeset patch # User Peter Sanchez # Date 1443996739 25200 # Sun Oct 04 15:12:19 2015 -0700 # Node ID a00cb277fb8ef123dfa3cb376d55124230e6fdd0 # Parent 0b40ba14f7217417a27a0ef334f269ebf7496f4e Added facebook module to login to FB for retrieval of Tinder access token. diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ url='https://bitbucket.org/netlandish/pytinder/', long_description=long_description, platforms=['any'], - install_requires=['requests>=2.6.0'], + install_requires=['requests>=2.6.0', 'beautifulsoup4>=4.4.1'], classifiers=[ 'Development Status :: 4 - Beta', 'Intended Audience :: Developers', diff --git a/tinder/facebook.py b/tinder/facebook.py new file mode 100644 --- /dev/null +++ b/tinder/facebook.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- +''' This module will emulate a FB login + + Taken, and adapted, from django-oauth-tokens + https://github.com/ramusus/django-oauth-tokens +''' +import requests +from bs4 import BeautifulSoup +from abc import ABCMeta, abstractproperty, abstractmethod + + +class AuthRequestBase(object): + + __metaclass__ = ABCMeta + + authorize_form_attributes = {} + + session = None + headers = {} + + @abstractproperty + def form_action_domain(self): + pass + + def __init__(self, username, password): + self.session = requests.Session() + + self.username = username + self.password = password + + def authorized_request(self, method='get', **kwargs): + if method not in ['get', 'post']: + raise ValueError('Only `get` and `post` are allowed methods') + + if not self.session.cookies: + self.authorize() + + if self.session.cookies: + return getattr(self.session, method)( + headers=kwargs.pop('headers', self.headers), + **kwargs + ) + else: + raise ValueError('Session cookies are not defined') + + def authorize(self): + ''' Authorize and set self.session for next requests and return + response of last request + ''' + response = self.session.get(self.login_url, headers=self.headers) + + method, action, data = self.get_form_data_from_content( + response.content, + **self.authorize_form_attributes + ) + + # submit auth form data + return self.session.post(action, data, headers=self.headers) + + def get_form_data(self, form): + data = {} + for input in form.findAll('input'): + if input.get('name'): + data[input.get('name')] = input.get('value') + + self.add_data_credentials(data) + + action = form.get('action') + if action[0] == '/': + action = self.form_action_domain + action + + return (form.get('method').lower(), action, data) + + @abstractmethod + def add_data_credentials(self, data): + pass + + def get_form_data_from_content(self, content, **kwargs): + bs = BeautifulSoup(content) + form = self.get_form_from_bs_content(bs, **kwargs) + return self.get_form_data(form) + + def get_form_from_bs_content(self, bs, **kwargs): + form = bs.find('form', **kwargs) + if not form: + raise Exception('There is no any form in response') + return form + + +class FacebookAuthRequest(AuthRequestBase): + ''' Facebook authorized request class + ''' + provider = 'facebook' + form_action_domain = 'https://m.facebook.com' + login_url = 'https://m.facebook.com/login.php' + headers = { + 'User-Agent': ('Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 ' + '(KHTML, like Gecko) Ubuntu Chromium/34.0.1847.116 ' + 'Chrome/34.0.1847.116 Safari/537.36'), + 'Upgrade-Insecure-Requests': 1, + 'Accept': ('text/html,application/xhtml+xml,application/xml;q=0.9,' + 'image/webp,*/*;q=0.8'), + 'Accept-Charset': 'utf-8;q=0.7,*;q=0.3', + 'Accept-Encoding': 'gzip,deflate,sdch', + 'Accept-Language': 'en-US,en;q=0.8', + 'Connection': 'keep-alive', + 'Host': 'www.facebook.com', + } + + account_locked_phrases = [ + 'Ваш аккаунт временно заблокирован', + ('Мы заблокировали ваш аккаунт в связи ' + 'с попыткой входа из незнакомого ' + 'места. Пожалуйста, помогите нам ' + 'подтвердить, что попытка входа была i' + 'произведена вами.'), + 'Your account is temporarily locked.', + ] + + def add_data_credentials(self, data): + data['email'] = self.username + data['pass'] = self.password + + def authorize(self): + response = super(FacebookAuthRequest, self).authorize() + + if 'Cookies Required' in response.content: + self.session.get(self.form_action_domain) + response = super(FacebookAuthRequest, self).authorize() + if 'Cookies Required' in response.content: + raise Exception("Facebook 'Cookies required' error") + + if 'You are trying too often' in response.content: + raise Exception(('Facebook authorization request returns error ' + '\'You are trying too often\'')) + + # TODO: move this to FacebookAcessToken class + if 'API Error Code: 191' in response.content: + raise ImproperlyConfigured( + ('You must specify URL \'{0}\' in your facebook application ' + 'settings').format(self.redirect_uri) + ) + + for account_locked_phrase in self.account_locked_phrases: + if account_locked_phrase in response.content: + raise AccountLocked( + ('Facebook errored \'Your account is temporarily locked.\'' + ' Try to login via web browser') + ) + + return response diff --git a/tinder/utils.py b/tinder/utils.py --- a/tinder/utils.py +++ b/tinder/utils.py @@ -1,9 +1,11 @@ import os +import re import stat import json import datetime import requests from . import constants +from .facebook import FacebookAuthRequest def pull_date(date_str): @@ -47,3 +49,26 @@ from . import api # Circular auth = load_auth_from_file(fname) return api.API(auth_handler=auth) + + +def get_tinder_access_token(username, password): + ''' Helper to login to your FB account and authorize + the Tinder App to get an access token that will work + with the Tinder API. + ''' + fb_url = ('https://www.facebook.com/dialog/oauth?client_id=464891386855067' + '&redirect_uri=https://www.facebook.com/connect/login_success.' + 'html&scope=basic_info,email,public_profile,user_about_me,' + 'user_activities,user_birthday,user_education_history,' + 'user_friends,user_interests,user_likes,user_location,' + 'user_photos,user_relationship_details&response_type=token') + req = FacebookAuthRequest(username=username, password=password) + response = req.authorized_request(url=fb_url) + if response.status_code != 200: + raise ValueError('Error logging in {0}'.format(response.content)) + + location = response.history[0].headers['location'] + res = re.search(r'.*access_token=(\w+)\&.*', location) + if not res: + raise ValueError('Unable to get access key {0}'.format(location)) + return res.groups()[0]