20
20
except ImportError :
21
21
import json
22
22
23
- from .config import headers , headers_and , headers_ios , cookies_file
23
+ from .config import headers , headers_and , headers_ios , flash_agent , cookies_file
24
24
from .log import logger
25
25
from .urls import urls
26
26
from .exceptions import (FutError , ExpiredSession , InternalServerError ,
27
27
UnknownError , PermissionDenied , Captcha ,
28
28
Conflict , MaxSessions , MultipleSession ,
29
- FeatureDisabled , doLoginFail )
29
+ FeatureDisabled , doLoginFail , NoUltimateTeam )
30
30
from .EAHashingAlgorithm import EAHashingAlgorithm
31
31
32
32
@@ -50,35 +50,16 @@ def baseId(resource_id, return_version=False):
50
50
return abs (resource_id )
51
51
52
52
53
- def itemParse (item_data ):
53
+ def itemParse (item_data , full = True ):
54
54
"""Parser for item data. Returns nice dictionary."""
55
- return {
55
+ # TODO: parse all data
56
+ item_data = {
56
57
'tradeId' : item_data .get ('tradeId' ),
57
58
'buyNowPrice' : item_data .get ('buyNowPrice' ),
58
59
'tradeState' : item_data .get ('tradeState' ),
59
60
'bidState' : item_data .get ('bidState' ),
60
61
'startingBid' : item_data .get ('startingBid' ),
61
62
'id' : item_data ['itemData' ]['id' ],
62
- 'timestamp' : item_data ['itemData' ]['timestamp' ], # auction start
63
- 'rating' : item_data ['itemData' ]['rating' ],
64
- 'assetId' : item_data ['itemData' ]['assetId' ],
65
- 'resourceId' : item_data ['itemData' ]['resourceId' ],
66
- 'itemState' : item_data ['itemData' ]['itemState' ],
67
- 'rareflag' : item_data ['itemData' ]['rareflag' ],
68
- 'formation' : item_data ['itemData' ]['formation' ],
69
- 'leagueId' : item_data ['itemData' ].get ('leagueId' ),
70
- 'injuryType' : item_data ['itemData' ].get ('injuryType' ),
71
- 'injuryGames' : item_data ['itemData' ]['injuryGames' ],
72
- 'lastSalePrice' : item_data ['itemData' ]['lastSalePrice' ],
73
- 'fitness' : item_data ['itemData' ]['fitness' ],
74
- 'training' : item_data ['itemData' ]['training' ],
75
- 'suspension' : item_data ['itemData' ]['suspension' ],
76
- 'contract' : item_data ['itemData' ]['contract' ],
77
- # 'position': item_data['itemData']['preferredPosition'],
78
- 'playStyle' : item_data ['itemData' ].get ('playStyle' ), # used only for players
79
- 'discardValue' : item_data ['itemData' ]['discardValue' ],
80
- 'itemType' : item_data ['itemData' ]['itemType' ],
81
- 'owners' : item_data ['itemData' ]['owners' ],
82
63
'offers' : item_data .get ('offers' ),
83
64
'currentBid' : item_data .get ('currentBid' ),
84
65
'expires' : item_data .get ('expires' ), # seconds left
@@ -87,6 +68,32 @@ def itemParse(item_data):
87
68
'sellerName' : item_data .get ('sellerName' ),
88
69
'watched' : item_data .get ('watched' ),
89
70
}
71
+ if full :
72
+ item_data .update ({
73
+ 'timestamp' : item_data ['itemData' ]['timestamp' ], # auction start
74
+ 'rating' : item_data ['itemData' ]['rating' ],
75
+ 'assetId' : item_data ['itemData' ]['assetId' ],
76
+ 'resourceId' : item_data ['itemData' ]['resourceId' ],
77
+ 'itemState' : item_data ['itemData' ]['itemState' ],
78
+ 'rareflag' : item_data ['itemData' ]['rareflag' ],
79
+ 'formation' : item_data ['itemData' ]['formation' ],
80
+ 'leagueId' : item_data ['itemData' ].get ('leagueId' ),
81
+ 'injuryType' : item_data ['itemData' ].get ('injuryType' ),
82
+ 'injuryGames' : item_data ['itemData' ]['injuryGames' ],
83
+ 'lastSalePrice' : item_data ['itemData' ]['lastSalePrice' ],
84
+ 'fitness' : item_data ['itemData' ]['fitness' ],
85
+ 'training' : item_data ['itemData' ]['training' ],
86
+ 'suspension' : item_data ['itemData' ]['suspension' ],
87
+ 'contract' : item_data ['itemData' ]['contract' ],
88
+ # 'position': item_data['itemData']['preferredPosition'],
89
+ 'playStyle' : item_data ['itemData' ].get ('playStyle' ), # used only for players
90
+ 'discardValue' : item_data ['itemData' ]['discardValue' ],
91
+ 'itemType' : item_data ['itemData' ]['itemType' ],
92
+ 'cardType' : item_data ['itemData' ].get ("cardsubtypeid" ), # used only for cards
93
+ 'owners' : item_data ['itemData' ]['owners' ],
94
+ })
95
+ return item_data
96
+
90
97
91
98
''' # different urls (platforms)
92
99
def cardInfo(resource_id):
@@ -96,6 +103,7 @@ def cardInfo(resource_id):
96
103
return requests.get(url).json()
97
104
'''
98
105
106
+
99
107
# TODO: optimize messages, xml parser might be faster
100
108
def nations ():
101
109
rc = requests .get (urls ('pc' )['messages' ]).content
@@ -105,6 +113,7 @@ def nations():
105
113
nations [int (i [0 ])] = i [1 ]
106
114
return nations
107
115
116
+
108
117
def leagues (year = 2016 ):
109
118
rc = requests .get (urls ('pc' )['messages' ]).content
110
119
data = re .findall ('<trans-unit resname="global.leagueFull.%s.league([0-9]+)">\n <source>(.+)</source>' % year , rc )
@@ -113,6 +122,7 @@ def leagues(year=2016):
113
122
leagues [int (i [0 ])] = i [1 ]
114
123
return leagues
115
124
125
+
116
126
def teams (year = 2016 ):
117
127
rc = requests .get (urls ('pc' )['messages' ]).content
118
128
data = re .findall ('<trans-unit resname="global.teamFull.%s.team([0-9]+)">\n <source>(.+)</source>' % year , rc )
@@ -121,6 +131,7 @@ def teams(year=2016):
121
131
teams [int (i [0 ])] = i [1 ]
122
132
return teams
123
133
134
+
124
135
class Core (object ):
125
136
def __init__ (self , email , passwd , secret_answer , platform = 'pc' , code = None , emulate = None , debug = False , cookies = cookies_file ):
126
137
self .cookies_file = cookies # TODO: map self.cookies to requests.Session.cookies?
@@ -167,16 +178,16 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, emul
167
178
game_sku = 'FFA16PS4'
168
179
platform = 'ps3' # ps4 not available?
169
180
else :
170
- raise FutError ('Wrong platform. (Valid ones are pc/xbox/xbox360/ps3/ps4)' )
181
+ raise FutError (reason = 'Wrong platform. (Valid ones are pc/xbox/xbox360/ps3/ps4)' )
171
182
# if self.r.get(self.urls['main_site']+'/fifa/api/isUserLoggedIn').json()['isLoggedIn']:
172
183
# return True # no need to log in again
173
184
# emulate
174
185
if emulate == 'ios' :
175
186
sku = 'FUT16IOS'
176
- clientVersion = 11
187
+ clientVersion = 15
177
188
elif emulate == 'and' :
178
189
sku = 'FUT16AND'
179
- clientVersion = 11
190
+ clientVersion = 15
180
191
# TODO: need more info about log in procedure in game
181
192
# elif emulate == 'xbox':
182
193
# sku = 'FFA16XBX' # FFA14CAP ?
@@ -191,7 +202,7 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, emul
191
202
sku = 'FUT16WEB'
192
203
clientVersion = 1
193
204
else :
194
- raise FutError ('Invalid emulate parameter. (Valid ones are and/ios).' ) # pc/ps3/xbox/
205
+ raise FutError (reason = 'Invalid emulate parameter. (Valid ones are and/ios).' ) # pc/ps3/xbox/
195
206
# === login
196
207
self .urls ['login' ] = self .r .get (self .urls ['fut_home' ]).url
197
208
self .r .headers ['Referer' ] = self .urls ['login' ] # prepare headers
@@ -215,21 +226,21 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, emul
215
226
# TODO: pick code from codes.txt?
216
227
if not code :
217
228
self .saveSession ()
218
- raise FutError ('Error during login process - code is required.' )
229
+ raise FutError (reason = 'Error during login process - code is required.' )
219
230
self .r .headers ['Referer' ] = url = rc .url
220
231
# self.r.headers['Upgrade-Insecure-Requests'] = 1 # ?
221
232
# self.r.headers['Origin'] = 'https://signin.ea.com'
222
233
rc = self .r .post (url , {'twofactorCode' : code , '_trustThisDevice' : 'on' , 'trustThisDevice' : 'on' , '_eventId' : 'submit' }).text
223
234
if 'Incorrect code entered' in rc or 'Please enter a valid security code' in rc :
224
- raise FutError ('Error during login process - provided code is incorrect.' )
235
+ raise FutError (reason = 'Error during login process - provided code is incorrect.' )
225
236
self .logger .debug (rc )
226
237
if 'Set Up an App Authenticator' in rc :
227
238
rc = self .r .post (url .replace ('s2' , 's3' ), {'_eventId' : 'cancel' , 'appDevice' : 'IPHONE' }).text
228
239
self .logger .debug (rc )
229
240
230
241
self .r .headers ['Referer' ] = self .urls ['login' ]
231
242
if self .r .get (self .urls ['main_site' ] + '/fifa/api/isUserLoggedIn' ).json ()['isLoggedIn' ] is not True : # TODO: parse error?
232
- raise FutError ('Error during login process (probably invalid email, password or code).' )
243
+ raise FutError (reason = 'Error during login process (probably invalid email, password or code).' )
233
244
# TODO: catch invalid data exception
234
245
# self.nucleus_id = re.search('userid : "([0-9]+)"', rc.text).group(1) # we'll get it later
235
246
@@ -239,7 +250,7 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, emul
239
250
self .logger .debug (rc .content )
240
251
rc = rc .text
241
252
# if 'EASW_ID' not in rc:
242
- # raise FutError('Error during login process (probably invalid email or password).')
253
+ # raise FutError(reason= 'Error during login process (probably invalid email or password).')
243
254
self .nucleus_id = re .search ("var EASW_ID = '([0-9]+)';" , rc ).group (1 )
244
255
self .build_cl = re .search ("var BUILD_CL = '([0-9]+)';" , rc ).group (1 )
245
256
# self.urls['fut_base'] = re.search("var BASE_FUT_URL = '(https://.+?)';", rc).group(1)
@@ -259,12 +270,17 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, emul
259
270
})
260
271
rc = self .r .get (self .urls ['acc_info' ], params = {'_' : int (time () * 1000 )})
261
272
self .logger .debug (rc .content )
262
- rc = rc .json ()['userAccountInfo' ]['personas' ][0 ]
263
- self .persona_id = rc ['personaId' ]
264
- self .persona_name = rc ['personaName' ]
265
- self .clubs = [i for i in rc ['userClubList' ]]
266
- # sort clubs by lastAccessTime (latest first)
267
- self .clubs .sort (key = lambda i : i ['lastAccessTime' ], reverse = True )
273
+ # pick persona (first valid for given game_sku)
274
+ personas = rc .json ()['userAccountInfo' ]['personas' ]
275
+ for p in personas :
276
+ # self.clubs = [i for i in p['userClubList']]
277
+ # sort clubs by lastAccessTime (latest first)
278
+ # self.clubs.sort(key=lambda i: i['lastAccessTime'], reverse=True)
279
+ for c in p ['userClubList' ]:
280
+ if game_sku in c ['skuAccessList' ]:
281
+ self .persona_id = p ['personaId' ]
282
+ self .persona_name = p ['personaName' ]
283
+ break
268
284
269
285
# authorization
270
286
self .r .headers .update ({ # prepare headers
@@ -314,7 +330,7 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, emul
314
330
rc = rc .json ()
315
331
if rc ['string' ] != 'OK' : # we've got error
316
332
if 'Answers do not match' in rc ['reason' ]:
317
- raise FutError ('Error during login process (invalid secret answer).' )
333
+ raise FutError (reason = 'Error during login process (invalid secret answer).' )
318
334
else :
319
335
raise UnknownError
320
336
self .r .headers ['Content-Type' ] = 'application/json'
@@ -326,7 +342,7 @@ def __login__(self, email, passwd, secret_answer, platform='pc', code=None, emul
326
342
del self .r .headers ['X-UT-Route' ]
327
343
self .r .headers .update ({
328
344
# 'X-HTTP-Method-Override': 'GET', # __request__ method manages this
329
- 'X-Requested-With' : 'ShockwaveFlash/19.0.0.162' ,
345
+ 'X-Requested-With' : flash_agent ,
330
346
'Referer' : 'https://www.easports.com/iframe/fut16/bundles/futweb/web/flash/FifaUltimateTeam.swf' ,
331
347
'Origin' : 'https://www.easports.com' ,
332
348
# 'Content-Type': 'application/json', # already set
@@ -366,20 +382,25 @@ def __request__(self, method, url, *args, **kwargs):
366
382
rc = rc .json ()
367
383
# error control
368
384
if 'code' and 'reason' in rc : # error
369
- if rc ['reason' ] == 'expired session' :
370
- raise ExpiredSession
371
- elif rc .get ('string' ) == 'Internal Server Error (ut)' :
372
- raise InternalServerError
373
- elif rc .get ('string' ) == 'Permission Denied' :
374
- raise PermissionDenied
375
- elif rc .get ('string' ) == 'Captcha Triggered' :
385
+ err_code = rc ['code' ]
386
+ err_reason = rc ['reason' ]
387
+ err_string = rc .get ('string' ) # "human readable" reason?
388
+ if err_reason == 'expired session' : # code?
389
+ raise ExpiredSession (err_code , err_reason , err_string )
390
+ elif err_code == '500' or err_string == 'Internal Server Error (ut)' :
391
+ raise InternalServerError (err_code , err_reason , err_string )
392
+ elif err_code == '489' or err_string == 'Feature Disabled' :
393
+ raise FeatureDisabled (err_code , err_reason , err_string )
394
+ elif err_code == '465' or err_string == 'No User' :
395
+ raise NoUltimateTeam (err_code , err_reason , err_string )
396
+ elif err_code == '461' or err_string == 'Permission Denied' :
397
+ raise PermissionDenied (err_code , err_reason , err_string )
398
+ elif err_code == '459' or err_string == 'Captcha Triggered' :
376
399
# img = self.r.get(self.urls['fut_captcha_img'], params={'_': int(time()*1000), 'token': captcha_token}).content # doesnt work - check headers
377
400
img = None
378
- raise Captcha (captcha_token , img )
379
- elif rc .get ('string' ) == 'Conflict' :
380
- raise Conflict
381
- elif rc .get ('string' ) == 'Feature Disabled' :
382
- raise FeatureDisabled
401
+ raise Captcha (err_code , err_reason , err_string , captcha_token , img )
402
+ elif err_code == '409' or err_string == 'Conflict' :
403
+ raise Conflict (err_code , err_reason , err_string )
383
404
else :
384
405
raise UnknownError (rc .__str__ ())
385
406
self .saveSession ()
@@ -515,7 +536,7 @@ def searchAuctions(self, ctype, level=None, category=None, assetId=None, defId=N
515
536
516
537
def bid (self , trade_id , bid ):
517
538
"""Make a bid."""
518
- rc = self .__get__ ( self . urls [ 'fut' ][ 'TradeStatus' ], params = { 'tradeIds' : trade_id })[ 'auctionInfo' ] [0 ]
539
+ rc = self .tradeStatus ( trade_id ) [0 ]
519
540
if rc ['currentBid' ] < bid and self .credits >= bid :
520
541
data = {'bid' : bid }
521
542
url = '{0}/{1}/bid' .format (self .urls ['fut' ]['PostBid' ], trade_id )
@@ -556,6 +577,14 @@ def squads(self):
556
577
return self.squad(squad_id='list')
557
578
'''
558
579
580
+ def tradeStatus (self , trade_id ):
581
+ if not isinstance (trade_id , (list , tuple )):
582
+ trade_id = (trade_id ,)
583
+ trade_id = (str (i ) for i in trade_id )
584
+ params = {'itemdata' : 'true' , 'tradeIds' : ',' .join (trade_id )}
585
+ rc = self .__get__ (self .urls ['fut' ]['TradeStatus' ], params = params )
586
+ return [itemParse (i , full = False ) for i in rc ['auctionInfo' ]]
587
+
559
588
def tradepile (self ):
560
589
"""Returns items in tradepile."""
561
590
rc = self .__get__ (self .urls ['fut' ]['TradePile' ])
@@ -642,6 +671,7 @@ def pileSize(self):
642
671
643
672
def stats (self ):
644
673
"""Returns all stats."""
674
+ # TODO: add self.urls['fut']['Stats']
645
675
# won-draw-loss
646
676
rc = self .__get__ (self .urls ['fut' ]['user' ])
647
677
data = {
0 commit comments