From 70efe426c2948c9ebe63e62a0677eebb5469bdeb Mon Sep 17 00:00:00 2001 From: Dan Gamble Date: Sat, 28 Oct 2017 13:12:12 +0100 Subject: [PATCH 01/27] Add a method that shows challenges by set This adds the ability to view the squad building challenges individually along with the requirements and awards --- fut/core.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fut/core.py b/fut/core.py index 13a0cd9..c68658b 100644 --- a/fut/core.py +++ b/fut/core.py @@ -1292,6 +1292,18 @@ def sbsSets(self): return rc # TODO?: parse + def sbsSetChallenges(self, set_id): + method = 'GET' + url = 'sbs/setId/%s/challenges' % set_id + + rc = self.__request__(method, url) + + # pinEvents + events = [self.pin.event('page_view', 'Hub - SBC')] + self.pin.send(events) + + return rc # TODO?: parse + def objectives(self, scope='all'): method = 'GET' url = 'user/dynamicobjectives' From 625ca12786cc05ff0eec8e80f98c664d99de0927 Mon Sep 17 00:00:00 2001 From: syndac Date: Mon, 30 Oct 2017 18:46:52 -0700 Subject: [PATCH 02/27] changed 'fut' to 'session' to avoid conflicts Conflicts occasionally occurred with module name 'fut' due to naming session Object 'fut' as well. --- README.rst | 58 +++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/README.rst b/README.rst index 63f1881..5d9bba0 100644 --- a/README.rst +++ b/README.rst @@ -76,7 +76,7 @@ Optional parameters: .. code-block:: python >>> import fut - >>> fut = fut.Core('email', 'password', 'secret answer') + >>> session = fut.Core('email', 'password', 'secret answer') Search ------ @@ -103,7 +103,7 @@ Optional parameters: .. code-block:: python - >>> items = fut.searchAuctions('development') + >>> items = session.searchAuctions('development') Bid --- @@ -114,7 +114,7 @@ Optional parameters: .. code-block:: python - >>> fut.bid(items[0]['trade_id'], 600) + >>> session.bid(items[0]['trade_id'], 600) Sell ---- @@ -126,7 +126,7 @@ Optional parameters: .. code-block:: python - >>> fut.sell(item['item_id'], 150) + >>> session.sell(item['item_id'], 150) Quick sell ---------- @@ -136,14 +136,14 @@ single item: .. code-block:: python >>> item_id = 123456789 - >>> fut.quickSell(item_id) + >>> session.quickSell(item_id) multiple items: .. code-block:: python >>> item_id = [123456789, 987654321] - >>> fut.quickSell(item_id) + >>> session.quickSell(item_id) Piles (Watchlist / Tradepile / Unassigned / Squad / Club) --------------------------------------------------------- @@ -151,24 +151,24 @@ Piles (Watchlist / Tradepile / Unassigned / Squad / Club) .. code-block:: python - >>> items = fut.tradepile() - >>> items = fut.unassigned() - >>> items = fut.squad() - >>> items = fut.club(count=10, level=10, type=1, start=0) - >>> items = fut.clubConsumablesDetails() - >>> fut.sendToTradepile(trade_id, item_id) # add card to tradepile - >>> fut.sendToClub(trade_id, item_id) # add card to club - >>> fut.sendToWatchlist(trade_id) # add card to watchlist - >>> fut.tradepileDelete(trade_id) # removes item from tradepile - >>> fut.watchlistDelete(trade_id) # removes item from watch list (you can pass single str/ing or list/tuple of ids - like in quickSell) - - >>> fut.tradepile_size # tradepile size (slots) + >>> items = session.tradepile() + >>> items = session.unassigned() + >>> items = session.squad() + >>> items = session.club(count=10, level=10, type=1, start=0) + >>> items = session.clubConsumablesDetails() + >>> session.sendToTradepile(trade_id, item_id) # add card to tradepile + >>> session.sendToClub(trade_id, item_id) # add card to club + >>> session.sendToWatchlist(trade_id) # add card to watchlist + >>> session.tradepileDelete(trade_id) # removes item from tradepile + >>> session.watchlistDelete(trade_id) # removes item from watch list (you can pass single str/ing or list/tuple of ids - like in quickSell) + + >>> session.tradepile_size # tradepile size (slots) 80 - >> len(fut.tradepile()) # tradepile fulfilment (number of cards in tradepile) + >> len(session.tradepile()) # tradepile fulfilment (number of cards in tradepile) 20 - >>> fut.watchlist_size # watchlist size (slots) + >>> session.watchlist_size # watchlist size (slots) 30 - >> len(fut.watchlist()) # watchlist fulfilment (number of cards in watchlist) + >> len(session.watchlist()) # watchlist fulfilment (number of cards in watchlist) 10 Credits @@ -178,7 +178,7 @@ It's cached on every request so if you want the most accurate info call fut.kepp .. code-block:: python - >>> fut.credits + >>> session.credits 600 Relist @@ -188,7 +188,7 @@ Relists all expired cards in tradepile. .. code-block:: python - >>> fut.relist() # relist all expired cards in tradepile + >>> session.relist() # relist all expired cards in tradepile Apply consumable ---------------- @@ -200,7 +200,7 @@ Apply consumable on player. .. code-block:: python - >>> fut.applyConsumable(item_id, resource_id) + >>> session.applyConsumable(item_id, resource_id) Card stats and definiction IDs ------------------------------ @@ -209,7 +209,7 @@ Returns stats and definition IDs for each card variation. .. code-block:: python - >>> fut.searchDefinition(asset_id, start=0, count=35) + >>> session.searchDefinition(asset_id, start=0, count=35) Keepalive --------- @@ -218,7 +218,7 @@ Sends keepalive ping and returns current credits amount (you have to make at lea .. code-block:: python - >>> fut.keepalive() + >>> session.keepalive() 650 Logout @@ -228,7 +228,7 @@ Logs out nicely (like clicking on logout button). .. code-block:: python - >>> fut.logout() + >>> session.logout() Database @@ -328,7 +328,7 @@ Make space in tradepile and just call one command to restore it: .. code-block:: python - fut.sendToTradepile(-1, id) + session.sendToTradepile(-1, id) I've got card with None tradeId so cannot move/trade it @@ -338,7 +338,7 @@ Make space in tradepile and just call one command to restore it: .. code-block:: python - fut.sendToTradepile(-1, id) + session.sendToTradepile(-1, id) PermissionDenied exceptions raises when trying to sell cards directly from watchlist From bb2d33317068de5250a75e5a7a10f54cca4ccd42 Mon Sep 17 00:00:00 2001 From: syndac Date: Mon, 30 Oct 2017 18:49:43 -0700 Subject: [PATCH 03/27] Added info about avoiding bans --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 5d9bba0..94b708f 100644 --- a/README.rst +++ b/README.rst @@ -321,6 +321,8 @@ Bans To avoid getting ban take a look at our little discussion/guide thread: https://github.com/oczkers/fut/issues/259 +Generally speaking, you should send no more than 500 requests per hour and 5000 requests per day. Be somewhat human. If you encounter a captcha, try to answer/solve it as soon as possible. + Somehow i've sent card to full tradepile and it disappeared ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 8151cf4ebf5ab8987b738679e608480d0225e187 Mon Sep 17 00:00:00 2001 From: syndac Date: Mon, 30 Oct 2017 18:52:24 -0700 Subject: [PATCH 04/27] Updated to reflect 'session' naming convention --- fut/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fut/__init__.py b/fut/__init__.py index ad36253..c02c55e 100644 --- a/fut/__init__.py +++ b/fut/__init__.py @@ -9,11 +9,11 @@ Basic usage: >>> import fut - >>> fifa = fut.Core('email', 'password', 'secret_answer') - >>> items = fut.searchAuctions('development') - >>> fut.bid(items[0]['trade_id'], 600) + >>> session = fut.Core('email', 'password', 'secret_answer') + >>> items = session.searchAuctions('development') + >>> session.bid(items[0]['trade_id'], 600) True - >>> fut.sell(item['item_id'], 150) + >>> session.sell(item['item_id'], 150) 1123321 From ce180a7812f09c7722a7aa3ef682815166d542d8 Mon Sep 17 00:00:00 2001 From: syndac Date: Mon, 30 Oct 2017 20:01:34 -0700 Subject: [PATCH 05/27] Added a little additional info for login --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 94b708f..568b25e 100644 --- a/README.rst +++ b/README.rst @@ -78,6 +78,8 @@ Optional parameters: >>> import fut >>> session = fut.Core('email', 'password', 'secret answer') +Be sure to set `platform=` to your platform and `sms=True` if you use SMS for 2 Factor Authentication. + Search ------ From f06a6e9da213c6b19ff636b56c06e97b95d16b1f Mon Sep 17 00:00:00 2001 From: syndac Date: Mon, 30 Oct 2017 20:04:30 -0700 Subject: [PATCH 06/27] Added note about sending to tradepile before selling --- README.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 568b25e..d309912 100644 --- a/README.rst +++ b/README.rst @@ -129,6 +129,7 @@ Optional parameters: .. code-block:: python >>> session.sell(item['item_id'], 150) +Before selling a newly-bought item, you have to send it to the tradpile. `session.sendToTradepile(item_id)` Quick sell ---------- @@ -158,7 +159,7 @@ Piles (Watchlist / Tradepile / Unassigned / Squad / Club) >>> items = session.squad() >>> items = session.club(count=10, level=10, type=1, start=0) >>> items = session.clubConsumablesDetails() - >>> session.sendToTradepile(trade_id, item_id) # add card to tradepile + >>> session.sendToTradepile(item_id) # add card to tradepile >>> session.sendToClub(trade_id, item_id) # add card to club >>> session.sendToWatchlist(trade_id) # add card to watchlist >>> session.tradepileDelete(trade_id) # removes item from tradepile From 7d203bea617d5e52d95c78b89005a9a1c47ee249 Mon Sep 17 00:00:00 2001 From: syndac Date: Mon, 30 Oct 2017 20:11:29 -0700 Subject: [PATCH 07/27] Fixed RST formatting of in-line code I just added --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index d309912..7c15208 100644 --- a/README.rst +++ b/README.rst @@ -78,7 +78,7 @@ Optional parameters: >>> import fut >>> session = fut.Core('email', 'password', 'secret answer') -Be sure to set `platform=` to your platform and `sms=True` if you use SMS for 2 Factor Authentication. +Be sure to set :code:`platform=` to your platform and :code:`sms=True` if you use SMS for 2 Factor Authentication. Search ------ @@ -129,7 +129,7 @@ Optional parameters: .. code-block:: python >>> session.sell(item['item_id'], 150) -Before selling a newly-bought item, you have to send it to the tradpile. `session.sendToTradepile(item_id)` +Before selling a newly-bought item, you have to send it to the tradpile. :code:`session.sendToTradepile(item_id)` Quick sell ---------- From e5afeb5f78b0c8e214559c383433cb45d110554f Mon Sep 17 00:00:00 2001 From: syndac Date: Thu, 2 Nov 2017 08:36:32 -0700 Subject: [PATCH 08/27] Added fix for SSLError to Readme --- README.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.rst b/README.rst index 7c15208..bafe450 100644 --- a/README.rst +++ b/README.rst @@ -318,6 +318,17 @@ to be continued ;-) Problems -------- +Getting "requests.exceptions.SSLError:....'utas.mob.v4.fut.ea.com' doesn't match 'utas.mobapp.fut.ea.com'"? +^^^^ +This is a new error, but here's a temporary fix to try: +1. Re-download the api from github +2. Go into fut/urls.py +3. On line 7, change :code:`auth_url = rc['authURL']` to :code:`auth_url = 'utas.mobapp.fut.ea.com'` +4. Run `python setup.py install` +5. Try your script again +6. **Please report in the Slack channel whether or not this worked!!** + + Bans ^^^^ From ca443ea509bdcb35343d1301406f0187e8b99d29 Mon Sep 17 00:00:00 2001 From: syndac Date: Thu, 2 Nov 2017 08:38:41 -0700 Subject: [PATCH 09/27] Fixed enumeration on readme for SSLError --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index bafe450..7ffd5ff 100644 --- a/README.rst +++ b/README.rst @@ -321,6 +321,7 @@ Problems Getting "requests.exceptions.SSLError:....'utas.mob.v4.fut.ea.com' doesn't match 'utas.mobapp.fut.ea.com'"? ^^^^ This is a new error, but here's a temporary fix to try: + 1. Re-download the api from github 2. Go into fut/urls.py 3. On line 7, change :code:`auth_url = rc['authURL']` to :code:`auth_url = 'utas.mobapp.fut.ea.com'` From 6c1e11816faf1e6e94847b080edbe9edddcded63 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 5 Nov 2017 16:19:12 +0100 Subject: [PATCH 10/27] php version reference fix #331 --- README.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 7ffd5ff..21f6172 100644 --- a/README.rst +++ b/README.rst @@ -50,6 +50,12 @@ Consumables database provided by koolaidjones: https://github.com/koolaidjones/F `Click here to get Slack invitation `_ +PHP ported version by InkedCurtis +--------------------------------- + +If You prefer php language, there is ported version made by InkedCurtis: https://github.com/InkedCurtis/FUT-API + + AutoBuyer GUI ------------- @@ -78,7 +84,7 @@ Optional parameters: >>> import fut >>> session = fut.Core('email', 'password', 'secret answer') -Be sure to set :code:`platform=` to your platform and :code:`sms=True` if you use SMS for 2 Factor Authentication. +Be sure to set :code:`platform=` to your platform and :code:`sms=True` if you use SMS for 2 Factor Authentication. Search ------ From a2dc3bf7f5f27e07cbfa50a7f32416b93e6b8413 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 5 Nov 2017 18:14:41 +0100 Subject: [PATCH 11/27] saving token is necessary --- .gitignore | 1 + fut/core.py | 177 ++++++++++++++++++++++++++++------------------------ 2 files changed, 96 insertions(+), 82 deletions(-) diff --git a/.gitignore b/.gitignore index 83f055c..73656eb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ fut.egg-info/ .coverage coverage.xml cookies.txt +token.txt test.py test_old.py testemu.py diff --git a/fut/core.py b/fut/core.py index c68658b..a9b9c68 100644 --- a/fut/core.py +++ b/fut/core.py @@ -288,10 +288,91 @@ def __init__(self, email, passwd, secret_answer, platform='pc', code=None, totp= logger(save=debug) # init root logger self.logger = logger(__name__) # TODO: validate fut request response (200 OK) - self.__login__(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) - def __login__(self, email, passwd, secret_answer, platform='pc', code=None, totp=None, sms=False, emulate=None, proxies=None): - """Log in. + def __login__(self, email, passwd, totp=None, sms=False): + """Log in - needed only if we don't have access token or it's expired.""" + params = {'prompt': 'login', + 'accessToken': 'null', + 'client_id': client_id, + 'response_type': 'token', + 'display': 'web2/login', + 'locale': 'en_US', + 'redirect_uri': 'https://www.easports.com/fifa/ultimate-team/web-app/auth.html', + 'scope': 'basic.identity offline signin'} + self.r.headers['Referer'] = 'https://www.easports.com/fifa/ultimate-team/web-app/' + rc = self.r.get('https://accounts.ea.com/connect/auth', params=params, timeout=self.timeout) + # TODO: validate (captcha etc.) + if rc.url != 'https://www.easports.com/fifa/ultimate-team/web-app/auth.html': # redirect target # this check is probably not needed + self.r.headers['Referer'] = rc.url + # origin required? + data = {'email': email, + 'password': passwd, + 'country': 'US', # is it important? + 'phoneNumber': '', # TODO: add phone code verification + 'passwordForPhone': '', + 'gCaptchaResponse': '', + 'isPhoneNumberLogin': 'false', # TODO: add phone login + 'isIncompletePhone': '', + '_rememberMe': 'on', + 'rememberMe': 'on', + '_eventId': 'submit'} + rc = self.r.post(rc.url, data=data, timeout=self.timeout) + # rc = rc.text + + if "'successfulLogin': false" in rc.text: + failedReason = re.search('general-error">\s+
\s+
\s+(.*)\s.+', rc.text).group(1) + # Your credentials are incorrect or have expired. Please try again or reset your password. + raise FutError(reason=failedReason) + + 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 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 + if totp: + rc = self.r.post(rc.url, {'_eventId': 'submit', 'codeType': 'APP'}) + code = pyotp.TOTP(totp).now() + elif sms: + rc = self.r.post(rc.url, {'_eventId': 'submit', 'codeType': 'SMS'}) + else: # email + rc = self.r.post(rc.url, {'_eventId': 'submit', 'codeType': 'EMAIL'}) + + # if 'We sent a security code to your' in rc.text or 'Your security code was sent to' in rc.text or 'Enter the 6-digit verification code' in rc.text or 'We have sent a security code' in rc.text: # post code + if 'Enter your security code' in rc.text: + # TODO: 'We sent a security code to your email' / 'We sent a security code to your ?' + # TODO: pick code from codes.txt? + if not code: + # self.saveSession() + # raise FutError(reason='Error during login process - code is required.') + code = input('Enter code: ') + self.r.headers['Referer'] = url = rc.url + # self.r.headers['Upgrade-Insecure-Requests'] = '1' # ? + # self.r.headers['Origin'] = 'https://signin.ea.com' + rc = self.r.post(url.replace('s3', 's4'), {'oneTimeCode': code, '_trustThisDevice': 'on', 'trustThisDevice': 'on', '_eventId': 'submit'}, timeout=self.timeout) + # rc = rc.text + if 'Incorrect code entered' in rc.text or 'Please enter a valid security code' in rc.text: + raise FutError(reason='Error during login process - provided code is incorrect.') + if 'Set Up an App Authenticator' in rc.text: # may we drop this? + rc = self.r.post(url.replace('s3', 's4'), {'_eventId': 'cancel', 'appDevice': 'IPHONE'}, timeout=self.timeout) + # rc = rc.text + + rc = re.match('https://www.easports.com/fifa/ultimate-team/web-app/auth.html#access_token=(.+?)&token_type=(.+?)&expires_in=[0-9]+', rc.url) + self.access_token = rc.group(1) + self.token_type = rc.group(2) + + self.saveSession() + + def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, totp=None, sms=False, emulate=None, proxies=None): + """Launch futweb :params email: Email. :params passwd: Password. @@ -314,6 +395,11 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, totp # load saved cookies/session if self.cookies_file: self.r.cookies = LWPCookieJar(self.cookies_file) + try: + with open('token.txt', 'r') as f: + self.token_type, self.access_token = f.readline().split(' ') + except FileNotFoundError: + self.__login__(email=email, passwd=passwd, totp=totp, sms=sms) try: self.r.cookies.load(ignore_discard=True) # is it good idea to load discarded cookies after long time? except IOError: @@ -366,82 +452,6 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, totp raise FutError(reason='Invalid emulate parameter. (Valid ones are and/ios).') # pc/ps3/xbox/ self.sku = sku # TODO: use self.sku in all class self.sku_a = 'FFT18' - params = {'prompt': 'login', - 'accessToken': 'null', - 'client_id': client_id, - 'response_type': 'token', - 'display': 'web2/login', - 'locale': 'en_US', - 'redirect_uri': 'https://www.easports.com/fifa/ultimate-team/web-app/auth.html', - 'scope': 'basic.identity offline signin'} - self.r.headers['Referer'] = 'https://www.easports.com/fifa/ultimate-team/web-app/' - rc = self.r.get('https://accounts.ea.com/connect/auth', params=params, timeout=self.timeout) - # TODO: validate (captcha etc.) - if rc.url != 'https://www.easports.com/fifa/ultimate-team/web-app/auth.html': # redirect target # TODO: move (only?) this to separate method - login and rename __login__ to launch - self.r.headers['Referer'] = rc.url - # origin required? - data = {'email': email, - 'password': passwd, - 'country': 'US', # is it important? - 'phoneNumber': '', # TODO: add phone code verification - 'passwordForPhone': '', - 'gCaptchaResponse': '', - 'isPhoneNumberLogin': 'false', # TODO: add phone login - 'isIncompletePhone': '', - '_rememberMe': 'on', - 'rememberMe': 'on', - '_eventId': 'submit'} - rc = self.r.post(rc.url, data=data, timeout=self.timeout) - # rc = rc.text - - if "'successfulLogin': false" in rc.text: - failedReason = re.search('general-error">\s+
\s+
\s+(.*)\s.+', rc.text).group(1) - # Your credentials are incorrect or have expired. Please try again or reset your password. - raise FutError(reason=failedReason) - - 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 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 - if totp: - rc = self.r.post(rc.url, {'_eventId': 'submit', 'codeType': 'APP'}) - code = pyotp.TOTP(totp).now() - elif sms: - rc = self.r.post(rc.url, {'_eventId': 'submit', 'codeType': 'SMS'}) - else: # email - rc = self.r.post(rc.url, {'_eventId': 'submit', 'codeType': 'EMAIL'}) - - # if 'We sent a security code to your' in rc.text or 'Your security code was sent to' in rc.text or 'Enter the 6-digit verification code' in rc.text or 'We have sent a security code' in rc.text: # post code - if 'Enter your security code' in rc.text: - # TODO: 'We sent a security code to your email' / 'We sent a security code to your ?' - # TODO: pick code from codes.txt? - if not code: - # self.saveSession() - # raise FutError(reason='Error during login process - code is required.') - code = input('Enter code: ') - self.r.headers['Referer'] = url = rc.url - # self.r.headers['Upgrade-Insecure-Requests'] = '1' # ? - # self.r.headers['Origin'] = 'https://signin.ea.com' - rc = self.r.post(url.replace('s3', 's4'), {'oneTimeCode': code, '_trustThisDevice': 'on', 'trustThisDevice': 'on', '_eventId': 'submit'}, timeout=self.timeout) - # rc = rc.text - if 'Incorrect code entered' in rc.text or 'Please enter a valid security code' in rc.text: - raise FutError(reason='Error during login process - provided code is incorrect.') - if 'Set Up an App Authenticator' in rc.text: # may we drop this? - rc = self.r.post(url.replace('s3', 's4'), {'_eventId': 'cancel', 'appDevice': 'IPHONE'}, timeout=self.timeout) - # rc = rc.text - - rc = re.match('https://www.easports.com/fifa/ultimate-team/web-app/auth.html#access_token=(.+?)&token_type=(.+?)&expires_in=[0-9]+', rc.url) - access_token = rc.group(1) - token_type = rc.group(2) # === launch futweb # TODO!: move to separate method __launch__, do not call login when not necessary @@ -452,8 +462,8 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, totp # TODO: config self.r.headers['Referer'] = 'https://www.easports.com/fifa/ultimate-team/web-app/' self.r.headers['Accept'] = 'application/json' - self.r.headers['Authorization'] = '%s %s' % (token_type, access_token) - rc = self.r.get('https://gateway.ea.com/proxy/identity/pids/me').json() + self.r.headers['Authorization'] = '%s %s' % (self.token_type, self.access_token) + rc = self.r.get('https://gateway.ea.com/proxy/identity/pids/me').json() # TODO: validate response self.nucleus_id = rc['pid']['externalRefValue'] # or pidId self.dob = rc['pid']['dob'] # tos_version = rc['tosVersion'] @@ -504,7 +514,7 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, totp params = {'client_id': 'FOS-SERVER', # i've seen in some js/json response but cannot find now 'redirect_uri': 'nucleus:rest', 'response_type': 'code', - 'access_token': access_token} + 'access_token': self.access_token} rc = self.r.get('https://accounts.ea.com/connect/auth', params=params).json() auth_code = rc['code'] @@ -695,6 +705,7 @@ def __sendToPile__(self, pile, trade_id=None, item_id=None): self.logger.info("{0} (itemId: {1}) moved to {2} Pile".format(trade_id, item_id, pile)) else: self.logger.error("{0} (itemId: {1}) NOT MOVED to {2} Pile. REASON: {3}".format(trade_id, item_id, pile, rc['itemData'][0]['reason'])) + # if rc['itemData'][0]['reason'] == 'Duplicate Item Type' and rc['itemData'][0]['errorCode'] == 472: # errorCode check is enought? return rc['itemData'][0]['success'] def logout(self, save=True): @@ -769,6 +780,8 @@ def saveSession(self): """Save cookies/session.""" if self.cookies_file: self.r.cookies.save(ignore_discard=True) + with open('token.txt', 'w') as f: + f.write('%s %s' % (self.token_type, self.access_token)) def baseId(self, *args, **kwargs): """Calculate base id and version from a resource id.""" From 04198a73516f74328b2c2c73b3f6bfb28bda756f Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 5 Nov 2017 18:21:48 +0100 Subject: [PATCH 12/27] remove \n\r --- fut/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fut/core.py b/fut/core.py index a9b9c68..218127c 100644 --- a/fut/core.py +++ b/fut/core.py @@ -397,7 +397,7 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot self.r.cookies = LWPCookieJar(self.cookies_file) try: with open('token.txt', 'r') as f: - self.token_type, self.access_token = f.readline().split(' ') + self.token_type, self.access_token = f.readline().replace('\n', '').replace('\r', '').split(' ') # removing \n \r just to make sure except FileNotFoundError: self.__login__(email=email, passwd=passwd, totp=totp, sms=sms) try: From 7a4e0e96453d7dafe1b4d93d034c35e82e941a38 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 5 Nov 2017 18:33:49 +0100 Subject: [PATCH 13/27] python2 --- fut/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fut/core.py b/fut/core.py index 218127c..61458e5 100644 --- a/fut/core.py +++ b/fut/core.py @@ -22,8 +22,9 @@ try: # python2 compatibility input = raw_input + FileNotFoundError except NameError: - pass + FileNotFoundError = IOError from .pin import Pin from .config import headers, headers_and, headers_ios, cookies_file, timeout, delay From 4db595705ebf99ea58fc0877b223bd8c89a70d5f Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Mon, 6 Nov 2017 11:32:26 +0100 Subject: [PATCH 14/27] relogin if token is invalid --- fut/core.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fut/core.py b/fut/core.py index 61458e5..d340e9c 100644 --- a/fut/core.py +++ b/fut/core.py @@ -465,6 +465,9 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot self.r.headers['Accept'] = 'application/json' self.r.headers['Authorization'] = '%s %s' % (self.token_type, self.access_token) rc = self.r.get('https://gateway.ea.com/proxy/identity/pids/me').json() # TODO: validate response + if rc.get('error') == 'invalid_access_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) self.nucleus_id = rc['pid']['externalRefValue'] # or pidId self.dob = rc['pid']['dob'] # tos_version = rc['tosVersion'] From 65c0ecf80aeede139178f2b055273528db19d168 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Mon, 6 Nov 2017 18:38:52 +0100 Subject: [PATCH 15/27] refresh token after login && add tradepileClear --- fut/core.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/fut/core.py b/fut/core.py index d340e9c..1f5b52a 100644 --- a/fut/core.py +++ b/fut/core.py @@ -369,6 +369,7 @@ def __login__(self, email, passwd, totp=None, sms=False): rc = re.match('https://www.easports.com/fifa/ultimate-team/web-app/auth.html#access_token=(.+?)&token_type=(.+?)&expires_in=[0-9]+', rc.url) self.access_token = rc.group(1) self.token_type = rc.group(2) + # TODO?: refresh after expires_in self.saveSession() @@ -466,6 +467,7 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot self.r.headers['Authorization'] = '%s %s' % (self.token_type, self.access_token) rc = self.r.get('https://gateway.ea.com/proxy/identity/pids/me').json() # TODO: validate response 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) self.nucleus_id = rc['pid']['externalRefValue'] # or pidId @@ -589,6 +591,16 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot self.tradepile_size = piles['tradepile'] self.watchlist_size = piles['watchlist'] + # refresh token + params = {'response_type': 'token', + 'redirect_uri': 'nucleus:rest', + 'prompt': 'none', + 'client_id': 'ORIGIN_JS_SDK'} + rc = self.r.get('https://accounts.ea.com/connect/auth', params=params).json() + self.access_token = rc['access_token'] + self.token_type = rc['token_type'] + # expired_in + self.saveSession() # pinEvents - home screen @@ -1130,6 +1142,14 @@ def tradepileDelete(self, trade_id): # item_id instead of trade_id? # TODO: validate status code return True + def tradepileClear(self): + """Removes all sold items from tradepile.""" + method = 'DELETE' + url = 'trade/sold' + + self.__request__(method, url) + # return True + def sendToTradepile(self, item_id, safe=True): """Send to tradepile (alias for __sendToPile__). From 48aa12a14363186635abee2b245f2eddd543525b Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Tue, 7 Nov 2017 11:43:42 +0100 Subject: [PATCH 16/27] add sbsSquad --- fut/core.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/fut/core.py b/fut/core.py index 1f5b52a..d392a50 100644 --- a/fut/core.py +++ b/fut/core.py @@ -1341,6 +1341,25 @@ def sbsSetChallenges(self, set_id): return rc # TODO?: parse + def sbsSquad(self, challenge_id): + method = 'GET' + url = 'sbs/challenge/%s/squad' % challenge_id + + rc = self.__request__(method, url) + + # pinEvents + events = [self.pin.event('page_view', 'SBC - Squad')] + self.pin.send(events) + + return rc + + # def sbsSquad(self, challenge_id, item_id): + # """Currenty only sending players to free space in squad.""" + # method = 'PUT' + # url = 'sbs/challenge/%s/squad' % challenge_id + # + # rc = self.__request__(method, url) + def objectives(self, scope='all'): method = 'GET' url = 'user/dynamicobjectives' From 69ebb2a464f943ca35922cd822a9031176bc1731 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Tue, 7 Nov 2017 11:44:11 +0100 Subject: [PATCH 17/27] correct pinevent on sbs challenges --- fut/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fut/core.py b/fut/core.py index d392a50..8ff1fb6 100644 --- a/fut/core.py +++ b/fut/core.py @@ -1336,7 +1336,7 @@ def sbsSetChallenges(self, set_id): rc = self.__request__(method, url) # pinEvents - events = [self.pin.event('page_view', 'Hub - SBC')] + events = [self.pin.event('page_view', 'SBC - Challenges')] self.pin.send(events) return rc # TODO?: parse From 8124fe5c49c8956bd93add9f98a2a9243e025d04 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Tue, 7 Nov 2017 12:17:29 +0100 Subject: [PATCH 18/27] add sendToSbs --- fut/core.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/fut/core.py b/fut/core.py index 8ff1fb6..17c0918 100644 --- a/fut/core.py +++ b/fut/core.py @@ -1177,7 +1177,33 @@ def sendToWatchlist(self, trade_id): data = {'auctionInfo': [{'id': trade_id}]} return self.__request__(method, url, data=json.dumps(data)) - # + + def sendToSbs(self, challenge_id, item_id): + """Send card to free slots in sbs squad.""" + # TODO?: multiple item_ids + method = 'PUT' + url = 'sbs/challenge/%s/squad' % challenge_id + + moved = False + n = 0 + squad = self.sbsSquad(challenge_id) + players = [] + for i in squad['squad']['players']: + if i['itemData']['id'] == 0 and not moved: + i['itemData']['id'] = item_id + moved = True + players.append({"index": n, + "itemData": {"id": i['itemData']['id'], + "dream": False}}) + n += 1 + data = {'players': players} + + if not moved: + return False + else: + self.__request__(method, url, data=json.dumps(data)) + return True + # def relist(self, clean=False): # """Relist all tradepile. Returns True or number of deleted (sold) if clean was set. # @@ -1353,13 +1379,6 @@ def sbsSquad(self, challenge_id): return rc - # def sbsSquad(self, challenge_id, item_id): - # """Currenty only sending players to free space in squad.""" - # method = 'PUT' - # url = 'sbs/challenge/%s/squad' % challenge_id - # - # rc = self.__request__(method, url) - def objectives(self, scope='all'): method = 'GET' url = 'user/dynamicobjectives' From 807889ce0187e93a2853b18490da13d63bed4c14 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Tue, 7 Nov 2017 12:17:46 +0100 Subject: [PATCH 19/27] add sendToSbs --- fut/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fut/core.py b/fut/core.py index 17c0918..1e3cb19 100644 --- a/fut/core.py +++ b/fut/core.py @@ -1179,7 +1179,7 @@ def sendToWatchlist(self, trade_id): return self.__request__(method, url, data=json.dumps(data)) def sendToSbs(self, challenge_id, item_id): - """Send card to free slots in sbs squad.""" + """Send card to first free slot in sbs squad.""" # TODO?: multiple item_ids method = 'PUT' url = 'sbs/challenge/%s/squad' % challenge_id From bec4db93baadb46edbedfaac480560e0690195a7 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Tue, 7 Nov 2017 12:56:55 +0100 Subject: [PATCH 20/27] fix #336 missing param in __login__ method --- fut/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fut/core.py b/fut/core.py index 1e3cb19..13c7bd7 100644 --- a/fut/core.py +++ b/fut/core.py @@ -291,7 +291,7 @@ def __init__(self, email, passwd, secret_answer, platform='pc', code=None, totp= # 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) - def __login__(self, email, passwd, totp=None, sms=False): + 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.""" params = {'prompt': 'login', 'accessToken': 'null', @@ -401,7 +401,7 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot with open('token.txt', 'r') as f: self.token_type, self.access_token = f.readline().replace('\n', '').replace('\r', '').split(' ') # removing \n \r just to make sure except FileNotFoundError: - self.__login__(email=email, passwd=passwd, totp=totp, sms=sms) + self.__login__(email=email, passwd=passwd, code=code, totp=totp, sms=sms) try: self.r.cookies.load(ignore_discard=True) # is it good idea to load discarded cookies after long time? except IOError: From e1dfddcf788c7c39709eb341fe65e8ef4010f689 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Tue, 7 Nov 2017 13:46:33 +0100 Subject: [PATCH 21/27] sendToSbs: return False if item already in sbs --- fut/core.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/fut/core.py b/fut/core.py index 13c7bd7..ffddb67 100644 --- a/fut/core.py +++ b/fut/core.py @@ -1179,16 +1179,18 @@ def sendToWatchlist(self, trade_id): return self.__request__(method, url, data=json.dumps(data)) def sendToSbs(self, challenge_id, item_id): - """Send card to first free slot in sbs squad.""" + """Send card FROM CLUB to first free slot in sbs squad.""" # TODO?: multiple item_ids method = 'PUT' url = 'sbs/challenge/%s/squad' % challenge_id - moved = False - n = 0 squad = self.sbsSquad(challenge_id) players = [] + moved = False + n = 0 for i in squad['squad']['players']: + if i['itemData']['id'] == item_id: # item already in sbs # TODO?: report reason + return False if i['itemData']['id'] == 0 and not moved: i['itemData']['id'] = item_id moved = True From 607a0265031f96fcfbcfd95fb568e498414eb3d6 Mon Sep 17 00:00:00 2001 From: Jukka Sarasti Date: Thu, 9 Nov 2017 11:12:08 +0100 Subject: [PATCH 22/27] Make token file location and name configurable. --- fut/config.py | 1 + fut/core.py | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/fut/config.py b/fut/config.py index d9da9e2..4c9d0d1 100644 --- a/fut/config.py +++ b/fut/config.py @@ -38,5 +38,6 @@ cookies_file = 'cookies.txt' +token_file = 'token.txt' timeout = 15 # defaulf global timeout delay = (1, 3) # default mininum delay between requests (random range) diff --git a/fut/core.py b/fut/core.py index ffddb67..5da52b0 100644 --- a/fut/core.py +++ b/fut/core.py @@ -27,7 +27,7 @@ FileNotFoundError = IOError from .pin import Pin -from .config import headers, headers_and, headers_ios, cookies_file, timeout, delay +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 .exceptions import (FutError, ExpiredSession, InternalServerError, @@ -273,10 +273,11 @@ 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, 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): self.credits = 0 self.duplicates = [] self.cookies_file = cookies # TODO: map self.cookies to requests.Session.cookies? + self.token_file = token self.timeout = timeout self.delay = delay self.request_time = 0 @@ -398,7 +399,7 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot if self.cookies_file: self.r.cookies = LWPCookieJar(self.cookies_file) try: - with open('token.txt', 'r') as f: + with open(self.token_file, 'r') as f: self.token_type, self.access_token = f.readline().replace('\n', '').replace('\r', '').split(' ') # removing \n \r just to make sure except FileNotFoundError: self.__login__(email=email, passwd=passwd, code=code, totp=totp, sms=sms) @@ -796,7 +797,7 @@ def saveSession(self): """Save cookies/session.""" if self.cookies_file: self.r.cookies.save(ignore_discard=True) - with open('token.txt', 'w') as f: + with open(self.token_file, 'w') as f: f.write('%s %s' % (self.token_type, self.access_token)) def baseId(self, *args, **kwargs): From a52c055f497c22fd2d737cf2d63ecdb6de3183db Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 12 Nov 2017 02:49:58 +0100 Subject: [PATCH 23/27] bump version in pinEvents - temporary, it has to be dynamic --- fut/urls.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fut/urls.py b/fut/urls.py index 983dbc7..345c658 100644 --- a/fut/urls.py +++ b/fut/urls.py @@ -18,7 +18,7 @@ if rc['patch']['f'] != 1800000: print('>>> WARNING: patch version changed: %s' % rc['patch']['f']) -v = '18.0.0' +v = '18.0.4' if rc['futweb_maintenance']: raise FutError('Futweb maintenance, please retry in few minutes.') From 164c8850c3a0386c673c13d450f3b63531947c80 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 12 Nov 2017 11:30:33 +0100 Subject: [PATCH 24/27] dynamic version in pin && correct token refresh --- .gitignore | 1 + fut/core.py | 40 ++++++++++++++++++++++++++-------------- fut/pin.py | 5 +++-- fut/urls.py | 3 +-- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 73656eb..3548ffa 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ test.log testemu.py screenlog.0 codecov.yml +club_players.txt diff --git a/fut/core.py b/fut/core.py index ffddb67..e953eaa 100644 --- a/fut/core.py +++ b/fut/core.py @@ -127,6 +127,7 @@ def itemParse(item_data, full=True): 'marketDataMaxPrice': item_data['itemData'].get('marketDataMaxPrice'), 'count': item_data.get('count'), # consumables only (?) 'untradeableCount': item_data.get('untradeableCount'), # consumables only (?) + 'loans': item_data.get('loans'), }) if 'item' in item_data: # consumables only (?) return_data.update({ @@ -456,7 +457,18 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot self.sku_a = 'FFT18' # === launch futweb - # TODO!: move to separate method __launch__, do not call login when not necessary + params = {'accessToken': self.access_token, + 'client_id': 'FIFA-18-WEBCLIENT', + 'response_type': 'token', + 'display': 'web2/login', + 'locale': 'en_US', + 'redirect_uri': 'https://www.easports.com/fifa/ultimate-team/web-app/auth.html', + 'scope': 'basic.identity offline signin'} + rc = self.r.get('https://accounts.ea.com/connect/auth', params=params) + rc = re.match('https://www.easports.com/fifa/ultimate-team/web-app/auth.html#access_token=(.+?)&token_type=(.+?)&expires_in=[0-9]+', rc.url) + self.access_token = rc.group(1) + self.token_type = rc.group(2) + # self.r.headers['Referer'] = 'https://www.easports.com/fifa/ultimate-team/web-app/auth.html' rc = self.r.get('https://www.easports.com/fifa/ultimate-team/web-app/', timeout=self.timeout).text # year = re.search('fut_year = "([0-9]{4}])"', rc).group(1) # use this to construct urls, sku etc. @@ -553,11 +565,6 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot raise UnknownError(rc.__str__()) self.r.headers['X-UT-SID'] = self.sid = rc['sid'] - # init pin - self.pin = Pin(sid=self.sid, nucleus_id=self.nucleus_id, persona_id=self.persona_id, dob=self.dob[:-3], platform=platform) - events = [self.pin.event('login', status='success')] - self.pin.send(events) - # validate (secret question) self.r.headers['Easw-Session-Data-Nucleus-Id'] = self.nucleus_id rc = self.r.get('https://%s/ut/game/fifa18/phishing/question' % self.fut_host, params={'_': self._}, timeout=self.timeout).json() @@ -578,6 +585,11 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot self._ += 1 self.r.headers['X-UT-PHISHING-TOKEN'] = self.token = rc['token'] + # init pin + self.pin = Pin(sid=self.sid, nucleus_id=self.nucleus_id, persona_id=self.persona_id, dob=self.dob[:-3], platform=platform) + events = [self.pin.event('login', status='success')] + self.pin.send(events) + # get basic user info # TODO: parse usermassinfo and change _usermassinfo to userinfo # TODO?: usermassinfo as separate method && ability to refresh piles etc. @@ -592,13 +604,13 @@ def __launch__(self, email, passwd, secret_answer, platform='pc', code=None, tot self.watchlist_size = piles['watchlist'] # refresh token - params = {'response_type': 'token', - 'redirect_uri': 'nucleus:rest', - 'prompt': 'none', - 'client_id': 'ORIGIN_JS_SDK'} - rc = self.r.get('https://accounts.ea.com/connect/auth', params=params).json() - self.access_token = rc['access_token'] - self.token_type = rc['token_type'] + # params = {'response_type': 'token', + # 'redirect_uri': 'nucleus:rest', + # 'prompt': 'none', + # 'client_id': 'ORIGIN_JS_SDK'} + # rc = self.r.get('https://accounts.ea.com/connect/auth', params=params).json() + # self.access_token = rc['access_token'] + # self.token_type = rc['token_type'] # expired_in self.saveSession() @@ -638,7 +650,7 @@ def __request__(self, method, url, data={}, params={}, fast=False): time.sleep(max(self.request_time - time.time() + random.randrange(self.delay[0], self.delay[1] + 1), 0)) # respect minimum delay self.r.options(url, params=params) else: - time.sleep(max(self.request_time - time.time() + 1.3, 0)) # respect 1s minimum delay between requests + time.sleep(max(self.request_time - time.time() + 1.4, 0)) # respect 1s minimum delay between requests self.request_time = time.time() # save request time for delay calculations if method.upper() == 'GET': rc = self.r.get(url, data=data, params=params, timeout=self.timeout) diff --git a/fut/pin.py b/fut/pin.py index 6cbf70c..77934ec 100644 --- a/fut/pin.py +++ b/fut/pin.py @@ -16,7 +16,7 @@ from datetime import datetime from fut.config import headers -from fut.urls import pin_url, v +from fut.urls import pin_url from fut.exceptions import FutError @@ -36,6 +36,7 @@ def __init__(self, sku=None, sid='', nucleus_id=0, persona_id='', dob=False, pla self.plat = re.search('plat:"(.+?)"', rc).group(1) self.et = re.search('et:"(.+?)"', rc).group(1) self.pidt = re.search('pidt:"(.+?)"', rc).group(1) + self.v = re.search('APP_VERSION="([0-9\.]+)"', rc).group(1) self.r = requests.Session() self.r.headers = headers @@ -101,7 +102,7 @@ def send(self, events, fast=False): "tidt": self.tidt, "tid": self.sku, "rel": self.rel, - "v": v, + "v": self.v, "ts_post": self.__ts(), "sid": self.sid, "gid": self.gid, # convert to int? diff --git a/fut/urls.py b/fut/urls.py index 345c658..a9cc214 100644 --- a/fut/urls.py +++ b/fut/urls.py @@ -16,9 +16,8 @@ if rc['pin'] != {"b": True, "bf": 500, "bs": 10, "e": True, "r": 3, "rf": 300}: print('>>> WARNING: ping variables changed: %s' % rc['pin']) -if rc['patch']['f'] != 1800000: +if rc['patch']['f'] != 1800000: # this is probably dead value print('>>> WARNING: patch version changed: %s' % rc['patch']['f']) -v = '18.0.4' if rc['futweb_maintenance']: raise FutError('Futweb maintenance, please retry in few minutes.') From d54a5b421656e5318b4dea2fad075f6e846bcb46 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 12 Nov 2017 12:00:03 +0100 Subject: [PATCH 25/27] add clubConsumables (fix #171), correct pinevents in club --- fut/core.py | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/fut/core.py b/fut/core.py index f9f87f6..9140054 100644 --- a/fut/core.py +++ b/fut/core.py @@ -974,10 +974,12 @@ def club(self, sort='desc', ctype='player', defId='', start=0, count=91, level=N if start == 0: if ctype == 'player': pgid = 'Club - Players - List View' - elif ctype == 'item': - pgid = 'Club - Club Items - List View' - else: # TODO: THIS IS WRONG, detect all ctypes + elif ctype == 'staff': + pgid = 'Club - Staff - List View' + elif ctype in ('item', 'kit', 'ball', 'badge', 'stadium'): pgid = 'Club - Club Items - List View' + # else: # TODO: THIS IS probably WRONG, detect all ctypes + # pgid = 'Club - Club Items - List View' events = [self.pin.event('page_view', pgid)] self.pin.send(events) @@ -991,27 +993,21 @@ def clubStaff(self): rc = self.__request__(method, url) return rc # TODO?: parse - # def clubConsumables(self): - # """Return all consumables stats in dictionary.""" - # rc = self.__get__(self.urls['fut']['ClubConsumableSearch']) # or ClubConsumableStats? - # consumables = {} - # for i in rc: - # if i['contextValue'] == 1: - # level = 'gold' - # elif i['contextValue'] == 2: - # level = 'silver' - # elif i['contextValue'] == 3: - # level = 'bronze' - # consumables[i['type']] = {'level': level, - # 'type': i['type'], # need list of all types - # 'contextId': i['contextId'], # dunno what is it - # 'count': i['typeValue']} - # return consumables - - # def clubConsumablesDetails(self): - # """Return all consumables details.""" - # rc = self.__get__('{0}{1}'.format(self.urls['fut']['ClubConsumableSearch'], '/development')) - # return [{itemParse(i) for i in rc.get('itemData', ())}] + def clubConsumables(self, fast=False): + """Return all consumables from club.""" + method = 'GET' + url = 'club/consumables/development' + + rc = self.__request__(method, url) + + events = [self.pin.event('page_view', 'Hub - Club')] + self.pin.send(events, fast=fast) + events = [self.pin.event('page_view', 'Club - Consumables')] + self.pin.send(events, fast=fast) + events = [self.pin.event('page_view', 'Club - Consumables - List View')] + self.pin.send(events, fast=fast) + + return rc['itemData'] # TODO?: parse def squad(self, squad_id=0, persona_id=None): """Return a squad. From 6842f84f25ea5c2bbfe74b8b919226006fc534f5 Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 12 Nov 2017 12:01:36 +0100 Subject: [PATCH 26/27] clubConsumables: parse response --- fut/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fut/core.py b/fut/core.py index 9140054..1102c7c 100644 --- a/fut/core.py +++ b/fut/core.py @@ -1007,7 +1007,7 @@ def clubConsumables(self, fast=False): events = [self.pin.event('page_view', 'Club - Consumables - List View')] self.pin.send(events, fast=fast) - return rc['itemData'] # TODO?: parse + return [{itemParse(i) for i in rc.get('itemData', ())}] def squad(self, squad_id=0, persona_id=None): """Return a squad. From a9611e270b1dfa1eb596176a0b6ccaeb85b80a2d Mon Sep 17 00:00:00 2001 From: Piotr Staroszczyk Date: Sun, 12 Nov 2017 19:52:24 +0100 Subject: [PATCH 27/27] v0.3.6 --- CHANGELOG.rst | 12 ++++++++++++ fut/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e580a03..1d75d4f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,18 @@ Changelog --------- +0.3.6 (2017-11-12) +^^^^^^^^^^^^^^^^^^ + +* add sbsSetChallenges (thanks to dan-gamble #330) +* readme polish (thanks to syndac) +* add tradepileClear +* add sbsSquad +* add sendToSbs +* add clubConsumables +* correct version param in pinevents +* save token between logins (maybe cookies are not needed?) + 0.3.5 (2017-10-26) ^^^^^^^^^^^^^^^^^^ diff --git a/fut/__init__.py b/fut/__init__.py index c02c55e..24e49b4 100644 --- a/fut/__init__.py +++ b/fut/__init__.py @@ -23,7 +23,7 @@ """ __title__ = 'fut' -__version__ = '0.3.5' +__version__ = '0.3.6' __author__ = 'Piotr Staroszczyk' __author_email__ = 'piotr.staroszczyk@get24.org' __license__ = 'GNU GPL v3' diff --git a/setup.py b/setup.py index 0aa46e5..f421534 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ __title__ = 'fut' -__version__ = '0.3.5' +__version__ = '0.3.6' __author__ = 'Piotr Staroszczyk' __author_email__ = 'piotr.staroszczyk@get24.org' __license__ = 'GNU GPL v3'