Skip to content

Commit

Permalink
Merge branch 'master' into stable
Browse files Browse the repository at this point in the history
  • Loading branch information
oczkers committed Dec 7, 2017
2 parents 6ce3c73 + 8234a1e commit 3ee9e15
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 48 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ Changelog
---------


0.3.8 (2017-12-07)
^^^^^^^^^^^^^^^^^^

* add anti-captcha.com support (all credits go to kirov #353)
* add futbinPrice, futheadPrice
* add missing params to club method (#351)
* tidt & taxv moved to compiled_2.js

0.3.7 (2017-12-01)
^^^^^^^^^^^^^^^^^^

Expand Down
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ Optional parameters:
- debug: [True/False] enables debug.
- cookies: [filename] saves cookies after every request and load it from given file when restaring app (just like browser).
- proxies: [dict] http/socks proxies in requests's format http://docs.python-requests.org/en/master/user/advanced/#proxies
- anticaptcha_client_key: [str] API key for [Anti Captcha](https://anti-captcha.com/). Requires setting proxies. After you encounter Captcha exception while making a request you should reinitialize Core object (it will relaunch session and enter Captcha).

.. code-block:: python
Expand Down
2 changes: 1 addition & 1 deletion fut/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"""

__title__ = 'fut'
__version__ = '0.3.7'
__version__ = '0.3.8'
__author__ = 'Piotr Staroszczyk'
__author_email__ = 'piotr.staroszczyk@get24.org'
__license__ = 'GNU GPL v3'
Expand Down
196 changes: 152 additions & 44 deletions fut/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import time
import json
import pyotp
from python_anticaptcha import AnticaptchaClient, FunCaptchaTask, Proxy
from python_anticaptcha.exceptions import AnticatpchaException
# from datetime import datetime, timedelta
try:
from cookielib import LWPCookieJar
Expand All @@ -29,7 +31,7 @@
from .pin import Pin
from .config import headers, headers_and, headers_ios, cookies_file, token_file, timeout, delay
from .log import logger
from .urls import client_id, auth_url, card_info_url, messages_url
from .urls import client_id, auth_url, card_info_url, messages_url, fun_captcha_public_key
from .exceptions import (FutError, ExpiredSession, InternalServerError,
UnknownError, PermissionDenied, Captcha,
Conflict, MaxSessions, MultipleSession,
Expand Down Expand Up @@ -163,13 +165,12 @@ def itemParse(item_data, full=True):
return return_data


''' # different urls (platforms)
def cardInfo(resource_id):
"""Return card info."""
# TODO: add referer to headers (futweb)
url = '{0}{1}.json'.format(self.urls['card_info'], baseId(resource_id))
return requests.get(url, timeout=timeout).json()
'''
# different urls (platforms)
# def cardInfo(resource_id):
# """Return card info."""
# # TODO: add referer to headers (futweb)
# url = '{0}{1}.json'.format(self.urls['card_info'], baseId(resource_id))
# return requests.get(url, timeout=timeout).json()


# TODO: optimize messages (parse whole messages once!), xml parser might be faster
Expand Down Expand Up @@ -278,7 +279,7 @@ def playstyles(year=2018, timeout=timeout):


class Core(object):
def __init__(self, email, passwd, secret_answer, platform='pc', code=None, totp=None, sms=False, emulate=None, debug=False, cookies=cookies_file, token=token_file, timeout=timeout, delay=delay, proxies=None):
def __init__(self, email, passwd, secret_answer, platform='pc', code=None, totp=None, sms=False, emulate=None, debug=False, cookies=cookies_file, token=token_file, timeout=timeout, delay=delay, proxies=None, anticaptcha_client_key=None):
self.credits = 0
self.duplicates = []
self.cookies_file = cookies # TODO: map self.cookies to requests.Session.cookies?
Expand All @@ -288,14 +289,16 @@ def __init__(self, email, passwd, secret_answer, platform='pc', code=None, totp=
self.request_time = 0
# db
self._players = None
self._playstyles = None
self._nations = None
self._stadiums = None
self._leagues = {}
self._teams = {}
self._usermassinfo = {}
logger(save=debug) # init root logger
self.logger = logger(__name__)
# TODO: validate fut request response (200 OK)
self.__launch__(email, passwd, secret_answer, platform=platform, code=code, totp=totp, sms=sms, emulate=emulate, proxies=proxies)
self.__launch__(email, passwd, secret_answer, platform=platform, code=code, totp=totp, sms=sms, emulate=emulate, proxies=proxies, anticaptcha_client_key=anticaptcha_client_key)

def __login__(self, email, passwd, code=None, totp=None, sms=False):
"""Log in - needed only if we don't have access token or it's expired."""
Expand Down Expand Up @@ -335,13 +338,12 @@ def __login__(self, email, passwd, code=None, totp=None, sms=False):
if 'var redirectUri' in rc.text:
rc = self.r.get(rc.url, params={'_eventId': 'end'}) # initref param was missing here

''' # pops out only on first launch
if 'FIFA Ultimate Team</strong> needs to update your Account to help protect your gameplay experience.' in rc: # request email/sms code
self.r.headers['Referer'] = rc.url # s2
rc = self.r.post(rc.url.replace('s2', 's3'), {'_eventId': 'submit'}, timeout=self.timeout).content
self.r.headers['Referer'] = rc.url # s3
rc = self.r.post(rc.url, {'twofactorType': 'EMAIL', 'country': 0, 'phoneNumber': '', '_eventId': 'submit'}, timeout=self.timeout)
'''
# pops out only on first launch
# if 'FIFA Ultimate Team</strong> needs to update your Account to help protect your gameplay experience.' in rc: # request email/sms code
# self.r.headers['Referer'] = rc.url # s2
# rc = self.r.post(rc.url.replace('s2', 's3'), {'_eventId': 'submit'}, timeout=self.timeout).content
# self.r.headers['Referer'] = rc.url # s3
# rc = self.r.post(rc.url, {'twofactorType': 'EMAIL', 'country': 0, 'phoneNumber': '', '_eventId': 'submit'}, timeout=self.timeout)

# click button to send code
if 'Login Verification' in rc.text: # click button to get code sent
Expand Down Expand Up @@ -379,7 +381,7 @@ def __login__(self, email, passwd, code=None, totp=None, sms=False):

self.saveSession()

def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, totp=None, sms=False, emulate=None, proxies=None):
def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, totp=None, sms=False, emulate=None, proxies=None, anticaptcha_client_key=None):
"""Launch futweb
:params email: Email.
Expand Down Expand Up @@ -488,7 +490,7 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot
if rc.get('error') == 'invalid_access_token':
print('invalid token')
self.__login__(email=email, passwd=passwd, totp=totp, sms=sms)
return self.__launch__(email=email, passwd=passwd, secret_answer=secret_answer, platform=platform, code=code, totp=totp, sms=sms, emulate=emulate, proxies=proxies)
return self.__launch__(email=email, passwd=passwd, secret_answer=secret_answer, platform=platform, code=code, totp=totp, sms=sms, emulate=emulate, proxies=proxies, anticaptcha_client_key=anticaptcha_client_key)
self.nucleus_id = rc['pid']['externalRefValue'] # or pidId
self.dob = rc['pid']['dob']
# tos_version = rc['tosVersion']
Expand Down Expand Up @@ -577,8 +579,45 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot
rc = self.r.get('https://%s/ut/game/fifa18/phishing/question' % self.fut_host, params={'_': self._}, timeout=self.timeout).json()
self._ += 1
if rc.get('code') == '458':
raise Captcha()
elif rc.get('string') != 'Already answered question':
if anticaptcha_client_key:
if not proxies:
raise FutError('FunCaptcha requires a proxy. Add proxies param.')
self.logger.debug('Solving FunCaptcha...')
anticaptcha = AnticaptchaClient(anticaptcha_client_key)
attempt = 0
while True:
attempt += 1
if attempt > 10:
raise FutError('Can\'t send captcha.')
try:
self.logger.debug('Attempt #{}'.format(attempt))
task = FunCaptchaTask(
'https://www.easports.com',
fun_captcha_public_key,
proxy=Proxy.parse_url(proxies.get('http')),
user_agent=self.r.headers['User-Agent']
)
job = anticaptcha.createTask(task)
job.join()
fun_captcha_token = job.get_token_response()
self.logger.debug('FunCaptcha solved: {}'.format(fun_captcha_token))
self.__request__('POST', 'captcha/fun/validate', data=json.dumps({
'funCaptchaToken': fun_captcha_token,
}))
rc = self.r.get('https://%s/ut/game/fifa18/phishing/question' % self.fut_host, params={'_': self._}, timeout=self.timeout).json()
self._ += 1
break
except AnticatpchaException as e:
if e.error_code in ['ERROR_PROXY_CONNECT_REFUSED', 'ERROR_PROXY_CONNECT_TIMEOUT', 'ERROR_PROXY_READ_TIMEOUT', 'ERROR_PROXY_BANNED']:
self.logger.exception('AnticatpchaException ' + e.error_code)
time.sleep(10)
continue
else:
raise

else:
raise Captcha(code=rc.get('code'), string=rc.get('string'), reason=rc.get('reason'))
if rc.get('string') != 'Already answered question':
params = {'answer': secret_answer_hash}
rc = self.r.post('https://%s/ut/game/fifa18/phishing/validate' % self.fut_host, params=params, timeout=self.timeout).json()
if rc['string'] != 'OK': # we've got an error
Expand Down Expand Up @@ -640,13 +679,15 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot
# return self.r.get(self.urls['shards'], params={'_': int(time.time()*1000)}, timeout=self.timeout).json()
# # self.r.headers['X-UT-Route'] = self.urls['fut_pc']

def __request__(self, method, url, data={}, params={}, fast=False):
def __request__(self, method, url, data=None, params=None, fast=False):
"""Prepare headers and sends request. Returns response as a json object.
:params method: Rest method.
:params url: Url.
"""
# TODO: update credtis?
data = data or {}
params = params or {}
url = 'https://%s/ut/game/fifa18/%s' % (self.fut_host, url)

self.logger.debug("request: {0} data={1}; params={2}".format(url, data, params))
Expand Down Expand Up @@ -860,10 +901,11 @@ def searchDefinition(self, asset_id, start=0, count=46):

rc = self.__request__(method, url, params=params)

try:
return [itemParse({'itemData': i}) for i in rc['itemData']]
except:
raise UnknownError('Invalid definition response')
# try:
# return [itemParse({'itemData': i}) for i in rc['itemData']]
# except:
# raise UnknownError('Invalid definition response')
return [itemParse({'itemData': i}) for i in rc['itemData']]

def search(self, ctype, level=None, category=None, assetId=None, defId=None,
min_price=None, max_price=None, min_buy=None, max_buy=None,
Expand Down Expand Up @@ -914,21 +956,36 @@ def search(self, ctype, level=None, category=None, assetId=None, defId=None,
'num': page_size,
'type': ctype, # "type" namespace is reserved in python
}
if level: params['lev'] = level
if category: params['cat'] = category
if assetId: params['maskedDefId'] = assetId
if defId: params['definitionId'] = defId
if min_price: params['micr'] = min_price
if max_price: params['macr'] = max_price
if min_buy: params['minb'] = min_buy
if max_buy: params['maxb'] = max_buy
if league: params['leag'] = league
if club: params['team'] = club
if position: params['pos'] = position
if zone: params['zone'] = zone
if nationality: params['nat'] = nationality
if rare: params['rare'] = 'SP'
if playStyle: params['playStyle'] = playStyle
if level:
params['lev'] = level
if category:
params['cat'] = category
if assetId:
params['maskedDefId'] = assetId
if defId:
params['definitionId'] = defId
if min_price:
params['micr'] = min_price
if max_price:
params['macr'] = max_price
if min_buy:
params['minb'] = min_buy
if max_buy:
params['maxb'] = max_buy
if league:
params['leag'] = league
if club:
params['team'] = club
if position:
params['pos'] = position
if zone:
params['zone'] = zone
if nationality:
params['nat'] = nationality
if rare:
params['rare'] = 'SP'
if playStyle:
params['playStyle'] = playStyle

rc = self.__request__(method, url, params=params, fast=fast)

Expand All @@ -943,6 +1000,18 @@ def searchAuctions(self, *args, **kwargs):
"""Alias for search method, just to keep compatibility."""
return self.search(*args, **kwargs)

# def searchAll(self, *args, **kwargs):
# """Generator for search method. (Temporary until search won't be a genetor)"""
# n = 0
# while True:
# results = self.search(start=n, *args, **kwargs)
# # print(len(results))
# if len(results) < 16:
# return results
# else:
# n += 16
# yield results

def bid(self, trade_id, bid, fast=False):
"""Make a bid.
Expand All @@ -968,14 +1037,53 @@ def bid(self, trade_id, bid, fast=False):
else:
return False

def club(self, sort='desc', ctype='player', defId='', start=0, count=91, level=None):
"""Return items in your club, excluding consumables."""
def club(self, sort='desc', ctype='player', defId='', start=0, count=91,
level=None, category=None, assetId=None, league=None, club=None,
position=None, zone=None, nationality=None, rare=False, playStyle=None):
"""Return items in your club, excluding consumables.
:param ctype: [development / ? / ?] Card type.
:param level: (optional) [?/?/gold] Card level.
:param category: (optional) [fitness/?/?] Card category.
:param assetId: (optional) Asset id.
:param defId: (optional) Definition id.
:param min_price: (optional) Minimal price.
:param max_price: (optional) Maximum price.
:param min_buy: (optional) Minimal buy now price.
:param max_buy: (optional) Maximum buy now price.
:param league: (optional) League id.
:param club: (optional) Club id.
:param position: (optional) Position.
:param nationality: (optional) Nation id.
:param rare: (optional) [boolean] True for searching special cards.
:param playStyle: (optional) Play style.
:param start: (optional) Start page sent to server so it supposed to be 12/15, 24/30 etc. (default platform page_size*n)
:param page_size: (optional) Page size (items per page)
"""
method = 'GET'
url = 'club'

params = {'sort': sort, 'type': ctype, 'defId': defId, 'start': start, 'count': count}
if level:
params['level'] = level
params['lev'] = level
if category:
params['cat'] = category
if assetId:
params['maskedDefId'] = assetId
if league:
params['leag'] = league
if club:
params['team'] = club
if position:
params['pos'] = position
if zone:
params['zone'] = zone
if nationality:
params['nat'] = nationality
if rare:
params['rare'] = 'SP'
if playStyle:
params['playStyle'] = playStyle
rc = self.__request__(method, url, params=params)

# pinEvent
Expand Down
Loading

0 comments on commit 3ee9e15

Please sign in to comment.