diff --git a/core/bid.py b/core/bid.py index 1f8e92e..f51931a 100644 --- a/core/bid.py +++ b/core/bid.py @@ -35,18 +35,11 @@ def roundBid(bid): def bid(q, api, playerList, settings): pileFull = False auctionsWon = 0 - bidDetails = {} trades = {} + playersIds = {p.playerid : p for p in playerList} # dict of ids api.resetSession() - for item in playerList: - bidDetails[item['player']['id']] = { - 'maxBid': item['buy'], - 'sell': item['sell'], - 'binPrice': item['bin'] - } - for item in api.watchlist(): trades[item['tradeId']] = item['resourceId'] @@ -56,29 +49,33 @@ def bid(q, api, playerList, settings): # Log selling players for trade in tradepile: asset = api.cardInfo(trade['resourceId']) - if str(asset['Item']['ItemType']).startswith('Player'): - displayName = asset['Item']['CommonName'] if asset['Item']['CommonName'] else asset['Item']['LastName'] - else: - displayName = asset['Item']['Desc'] + try: + if str(asset['Item']['ItemType']).startswith('Player'): + displayName = asset['Item']['CommonName'] if asset['Item']['CommonName'] else asset['Item']['LastName'] + else: + displayName = asset['Item']['Desc'] + except: + displayName = "Unknown" + card = PlayerCard(trade, displayName) q.put((card, EventType.SELLING, api.credits)) - for defId in bidDetails.keys(): + for player in playerList: - if bidDetails[defId]['maxBid'] < 100: + if player.maxBuy < 100: continue try: # How many of this item do we already have listed? - listed = sum([str(api.baseId(item['resourceId'])) == defId for item in tradepile]) + listed = sum([str(api.baseId(item['resourceId'])) == player.playerid for item in tradepile]) # Only bid if we don't already have a full trade pile and don't own too many of this player binWon = False if not pileFull and api.credits > settings['minCredits'] and listed < settings['maxPlayer']: # Look for any BIN less than the BIN price - for item in api.searchAuctions('player', defId=defId, max_buy=bidDetails[defId]['maxBid'], start=0, page_size=50): + for item in api.searchAuctions('player', defId=player.playerid, max_buy=player.maxBuy, start=0, page_size=50): # player safety checks for every possible bid if listed >= settings['maxPlayer'] or api.credits < settings['minCredits']: break @@ -108,8 +105,8 @@ def bid(q, api, playerList, settings): # Search first 50 items in my price range to bid on within 5 minutes if not settings['snipeOnly']: bidon = 0 - subtract = decrement(bidDetails[defId]['maxBid']) - for item in api.searchAuctions('player', defId=defId, max_price=bidDetails[defId]['maxBid']-subtract, start=0, page_size=50): + subtract = decrement(player.maxBuy) + for item in api.searchAuctions('player', defId=player.playerid, max_price=player.maxBuy-subtract, start=0, page_size=50): # player safety checks for every possible bid # Let's look at last 5 minutes for now and bid on 5 players max if item['expires'] > 300 or bidon >= 5 or listed >= settings['maxPlayer'] or api.credits < settings['minCredits']: @@ -149,11 +146,13 @@ def bid(q, api, playerList, settings): for item in api.tradeStatus([tradeId for tradeId in trades]): item['resourceId'] = trades[item['tradeId']] baseId = str(abs(item['resourceId'] + 0x80000000)) - if baseId not in bidDetails: + + if baseId not in playersIds: continue - maxBid = bidDetails[baseId]['maxBid'] - sell = bidDetails[baseId]['sell'] - binPrice = bidDetails[baseId]['binPrice'] + + maxBid = playersIds[baseId].maxBuy + sell = playersIds[baseId].sell + binPrice = playersIds[baseId].bin # How many of this item do we already have listed? listed = sum([str(api.baseId(trade['resourceId'])) == baseId for trade in tradepile]) @@ -228,11 +227,11 @@ def bid(q, api, playerList, settings): if binWon: for item in api.unassigned(): baseId = str(abs(item['resourceId'] + 0x80000000)) - if baseId not in bidDetails: + if baseId not in playersIds: continue - maxBid = bidDetails[baseId]['maxBid'] - sell = bidDetails[baseId]['sell'] - binPrice = bidDetails[baseId]['binPrice'] + + sell = playersIds[baseId].sell + binPrice = playersIds[baseId].bin tradeId = item['tradeId'] if item['tradeId'] is not None else -1 asset = api.cardInfo(item['resourceId']) @@ -272,11 +271,18 @@ def bid(q, api, playerList, settings): q.put('%s Manually re-listing %d players.\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), expired)) for i in tradepile: baseId = str(abs(i['resourceId'] + 0x80000000)) - if baseId in bidDetails: - sell = i['startingBid'] if settings['relistAll'] else bidDetails[baseId]['sell'] - binPrice = i['buyNowPrice'] if settings['relistAll'] else bidDetails[baseId]['binPrice'] + if baseId in playersIds: + sell = i['startingBid'] if settings['relistAll'] else playersIds[baseId].sell + binPrice = i['buyNowPrice'] if settings['relistAll'] else playersIds[baseId].bin if i['tradeState'] == 'expired' and sell and binPrice: api.sell(i['id'], sell, binPrice) + else: + if i['tradeState'] == 'expired': + # If we don't follow this player, then just relist it with the same price + asset = api.cardInfo(i['resourceId']) + displayName = asset['Item']['CommonName'] if asset['Item']['CommonName'] else asset['Item']['LastName'] + q.put('%s Re-listing %s at the same price. (Player not in target list)\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), displayName)) + api.sell(i['id'], i['startingBid'], i['buyNowPrice']) # Log sold items sold = sum([i['tradeState'] == 'closed' for i in tradepile]) diff --git a/core/model/__init__.py b/core/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/model/player.py b/core/model/player.py new file mode 100644 index 0000000..a764e7e --- /dev/null +++ b/core/model/player.py @@ -0,0 +1,33 @@ +from json import JSONEncoder + +class Player(): + """ + Represents a player + """ + def __init__(self, item): + details = item['player'] # this is the original dictionary + + self.playerid = details['id'] + self.displayName = details['commonName'] if details['commonName'] is not '' else details['lastName'] + self.position = details['position'] + self.rating = details['rating'] + self.details = details + + self.maxBuy = item['buy'] + self.sell = item['sell'] + self.bin = item['bin'] + + try: + self.enabled = item['enabled'] + except: + self.enabled = 'yes' + +class PlayerEncoder(JSONEncoder): + def default(self, o): + return { + 'buy' : o.maxBuy, + 'sell': o.sell, + 'bin' : o.bin, + 'enabled': o.enabled, + 'player' : o.details + } diff --git a/frames/bid.py b/frames/bid.py index a557ad8..3c5fd12 100644 --- a/frames/bid.py +++ b/frames/bid.py @@ -183,6 +183,7 @@ def __init__(self, master, controller): self.clearErrors() def bid(self): + if not self._bidding: return if self.p is not None and self.p.is_alive(): @@ -200,7 +201,7 @@ def bid(self): self.p = mp.Process(target=bid, args=( self.q, self.controller.api, - self.args['playerList'], + self.userPlayers, self.settings )) self.p.start() @@ -253,32 +254,32 @@ def updatePrice(self): self.updateLog('%s Updating Prices for Player List...\n' % (time.strftime('%Y-%m-%d %H:%M:%S'))) self._updatedItems = [] # it takes around 3 searches per player, based on RPM - wait = (60/self.settings['rpm']) * 3 * len(self.args['playerList']) + wait = (60/self.settings['rpm']) * 3 * len(self.userPlayers) self.updateLog('%s This is going to take around %.1f minute(s)...\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), wait/60)) self.p = mp.Process(target=lowestBin, args=( self.q, self.controller.api, - [item['player']['id'] for item in self.args['playerList']] + [player.playerid for player in self.userPlayers] )) self.p.start() self._lastUpdate = time.time() self.after(int(wait*1000), self.bid) - def setPrice(self, item, sell): - item['buy'] = roundBid(sell*self.settings['buy']) - item['sell'] = roundBid(sell*self.settings['sell']) - item['bin'] = roundBid(sell*self.settings['bin']) - self.tree.set(item['player']['id'], 'buy', item['buy']) - self.tree.set(item['player']['id'], 'sell', item['sell']) - self.tree.set(item['player']['id'], 'bin', item['bin']) + def setPrice(self, player, sell): + player.maxBuy = roundBid(sell*self.settings['buy']) + player.sell = roundBid(sell*self.settings['sell']) + player.bin = roundBid(sell*self.settings['bin']) + self.tree.set(player.playerid, 'buy', player.maxBuy) + self.tree.set(player.playerid, 'sell', player.sell) + self.tree.set(player.playerid, 'bin', player.bin) playersearch = self.controller.get_frame(PlayerSearch) - playersearch.tree.set(item['player']['id'], 'buy', item['buy']) - playersearch.tree.set(item['player']['id'], 'sell', item['sell']) - playersearch.tree.set(item['player']['id'], 'bin', item['bin']) - self.save_list() - displayName = item['player']['commonName'] if item['player']['commonName'] is not '' else item['player']['lastName'] - self.updateLog('%s Setting %s to %d/%d/%d (based on %d)...\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), displayName, item['buy'], item['sell'], item['bin'], sell)) - return item + playersearch.tree.set(player.playerid, 'buy', player.maxBuy) + playersearch.tree.set(player.playerid, 'sell', player.sell) + playersearch.tree.set(player.playerid, 'bin', player.bin) + self.updateLog('%s Setting %s to %d/%d/%d (based on %d)...\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), + player.displayName, player.maxBuy, player.sell, + player.bin, sell)) + return player def lookup_bin(self, player): # lookup BIN @@ -326,7 +327,7 @@ def checkQueue(self): elif (msg[1] == EventType.BIDWON or msg[1] == EventType.BIN): self.auctionStatus.update_status(msg[0], time.strftime('%Y-%m-%d %H:%M:%S'), msg[0].currentBid, tag='won') elif msg[1] == EventType.SELLING: - self.auctionStatus.update_status(msg[0], time.strftime('%Y-%m-%d %H:%M:%S'), msg[0].currentBid, tag='selling') + self.auctionStatus.update_status(msg[0], time.strftime('%Y-%m-%d %H:%M:%S'), msg[0].currentBid, tag='selling', highlight=False) elif msg[1] == EventType.SOLD: self.auctionStatus.update_status(msg[0], time.strftime('%Y-%m-%d %H:%M:%S'), msg[0].currentBid, tag='sold') elif msg[1] == EventType.UPDATE: @@ -348,19 +349,18 @@ def checkQueue(self): elif isinstance(msg, dict): # Update Pricing self._lastUpdate = time.time() - for item in self.args['playerList']: + for player in self.userPlayers: # Skip those that are finished - if item['player']['id'] in self._updatedItems: + if player.playerid in self._updatedItems: continue - if item['player']['id'] == msg['defId']: - displayName = item['player']['commonName'] if item['player']['commonName'] is not '' else item['player']['lastName'] + if player.playerid == msg['defId']: if msg['num'] > 10: bid = msg['lowestBIN'] - increment(msg['lowestBIN']) - self.updateLog('%s %d %s listed... Lowering Bid to %d\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), msg['num'], displayName, bid)) + self.updateLog('%s %d %s listed... Lowering Bid to %d\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), msg['num'], player.displayName, bid)) else: bid = msg['lowestBIN'] - self.updateLog('%s %d %s listed... Setting Bid to %d\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), msg['num'], displayName, bid)) - item = self.setPrice(item, bid) + self.updateLog('%s %d %s listed... Setting Bid to %d\n' % (time.strftime('%Y-%m-%d %H:%M:%S'), msg['num'], player.displayName, bid)) + item = self.setPrice(player, bid) break else: # Normal Message @@ -385,11 +385,6 @@ def updateLog(self, msg): self.logView.see(tk.END) self.update_idletasks() - def save_list(self): - self.args['playerFile'][self.controller.user] = self.args['playerList'] - with open(constants.PLAYERS_FILE, 'w') as f: - json.dump(self.args['playerFile'], f) - def save_settings(self, *args): try: self.settings = { @@ -421,12 +416,9 @@ def active(self): self.updateLog('%s Set Bid Options...\n' % (time.strftime('%Y-%m-%d %H:%M:%S'))) self.controller.status.set_status('Set Bid Options...') self.tree.delete(*self.tree.get_children()) - for item in self.args['playerList']: - displayName = item['player']['commonName'] if item['player']['commonName'] is not '' else item['player']['lastName'] - try: - self.tree.insert('', 'end', item['player']['id'], text=displayName, values=(item['buy'], item['sell'], item['bin'])) - except: - pass + self.userPlayers = self.args['userPlayers'] + for player in self.userPlayers: + self.tree.insert('', 'end', player.playerid, text=player.displayName, values=(player.maxBuy, player.sell, player.bin)) self._lastUpdate = 0 self._updatedItems = [] diff --git a/frames/playersearch.py b/frames/playersearch.py index 96fe4cb..e280156 100644 --- a/frames/playersearch.py +++ b/frames/playersearch.py @@ -9,6 +9,7 @@ from frames.base import Base from PIL import Image, ImageTk from core.playercard import create +from core.model.player import Player, PlayerEncoder class PlayerSearch(Base): @@ -16,10 +17,16 @@ def __init__(self, master, controller): Base.__init__(self, master, controller) self.master = master self.url = 'https://www.easports.com/uk/fifa/ultimate-team/api/fut/item' + + # Track the current job to be able to interrupt it, if needed self._job = None - self.player = tk.StringVar() - self._playerName = '' - search = tk.Entry(self, textvariable=self.player) + + # Track the currently searched player name + self.searchedPlayerName = '' + + # Add search bar + self.issuedPlayerName = tk.StringVar() + search = tk.Entry(self, textvariable=self.issuedPlayerName) search.bind('', self.search) search.bind('', self.lookup) search.grid(column=0, row=0, columnspan=2, sticky='we') @@ -70,7 +77,7 @@ def _configure_canvas(event): canvas.bind('', _configure_canvas) # Add a treeview to display selected players - self.tree = EditableTreeview(self, columns=('position', 'rating', 'buy', 'sell', 'bin', 'actions'), selectmode='browse', height=8) + self.tree = EditableTreeview(self, columns=('position', 'rating', 'buy', 'sell', 'bin', 'enabled', 'actions'), selectmode='browse', height=8) self.tree.heading('#0', text='Name', anchor='w') self.tree.column('position', width=100, anchor='center') self.tree.heading('position', text='Position') @@ -82,6 +89,8 @@ def _configure_canvas(event): self.tree.heading('sell', text='Sell For') self.tree.column('bin', width=100, anchor='center') self.tree.heading('bin', text='Sell For BIN') + self.tree.column('enabled', width=50, anchor='center') + self.tree.heading('enabled', text='Enabled') self.tree.column('actions', width=20, anchor='center') self.tree.bind('<>', self._on_inplace_edit) self.tree.bind('<>', self._on_cell_edited) @@ -95,15 +104,8 @@ def _configure_canvas(event): self._del_btn = tk.Button(self.tree, text='-', command=self._on_del_clicked) - # Search for existing list - self._playerFile = {} - self._playerList = [] - try: - with open(constants.PLAYERS_FILE, 'r') as f: - self._playerFile = json.load(f) - self._playerList = [] - except: - pass + # Load existing players + self.load_players_from_file() self.grid_columnconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) @@ -113,29 +115,46 @@ def _configure_canvas(event): self.grid_rowconfigure(3, weight=0) self.grid_rowconfigure(4, weight=0) + def load_players_from_file(self): + try: + with open(constants.PLAYERS_FILE, 'r') as f: + self.allPlayers = json.load(f) + except Exception as e: + print("Error while loading players file") + self.allPlayers = {} + + # At this stage, allPlayers is a dictionary of lists of dictionaries + for user in self.allPlayers: + userPlayersList = self.allPlayers[user] + userPlayers = [Player(p) for p in userPlayersList] + self.allPlayers[user] = userPlayers + def search(self, event=None): self.kill_job() # make sure it's a different name - if self._playerName != self.player.get(): - self._playerName = self.player.get() + if self.searchedPlayerName != self.issuedPlayerName.get(): + self.searchedPlayerName = self.issuedPlayerName.get() self._job = self.after(500, self.lookup) def lookup(self, event=None): self.kill_job() - payload = {'jsonParamObject': json.dumps({'name': self._playerName})} + payload = {'jsonParamObject': json.dumps({'name': self.searchedPlayerName})} response = requests.get(self.url, params=payload).json() - self.controller.status.set_status('Found %d matches for "%s"' % (response['totalResults'], self._playerName)) + self.controller.status.set_status('Found %d matches for "%s"' % (response['totalResults'], self.searchedPlayerName)) for child in self.interior.winfo_children(): child.destroy() p = mp.Pool(processes=mp.cpu_count()) results = [p.apply_async(create, (player,)) for player in response['items']] self.master.config(cursor='wait') self.master.update() + + # Load a player card for each result i = 0 for r in results: self.load_player(r.get(), response['items'][i]) i += 1 + self.master.config(cursor='') self.master.update() @@ -147,28 +166,28 @@ def load_player(self, result, player): lbl.config(cursor='pencil') lbl.image = img self.update_idletasks() - lbl.bind("", lambda e, player=player: self.add_player({ - 'player': player, - 'buy': 0, - 'sell': 0, - 'bin': 0 - })) - - def add_player(self, item, write=True): - player = item['player'] - displayName = player['commonName'] if player['commonName'] is not '' else player['lastName'] + + # Bind an action that will add a player in the list on click + lbl.bind("", lambda e, player=player: self.add_player(Player( + { 'player': player, 'buy': 0, 'sell': 0, 'bin': 0 + }) + )) + + def add_player(self, player, write=True): try: - self.tree.insert('', 'end', player['id'], text=displayName, values=(player['position'], player['rating'], item['buy'], item['sell'], item['bin'])) - if write: - self._playerList.append(item) - self.save_list() + self.tree.insert('', 'end', player.playerid, text=player.displayName, values=(player.position, + player.rating, player.maxBuy, player.sell, player.bin, player.enabled)) except: pass - def save_list(self): - self._playerFile[self.controller.user] = self._playerList + if write: + self.userPlayers.append(player) + self.save_players_list() + + def save_players_list(self): + self.allPlayers[self.controller.user] = self.userPlayers with open(constants.PLAYERS_FILE, 'w') as f: - json.dump(self._playerFile, f) + json.dump(self.allPlayers, f, cls=PlayerEncoder) def _on_inplace_edit(self, event): col, item = self.tree.get_event_info() @@ -176,36 +195,44 @@ def _on_inplace_edit(self, event): self.tree.inplace_entry(col, item) elif col in ('actions',): self.tree.inplace_custom(col, item, self._del_btn) + elif col in ('enabled',): + self.tree.inplace_checkbutton(col, item, onvalue="yes", offvalue="no") def _on_del_clicked(self): sel = self.tree.selection() if sel: - item = sel[0] - self.tree.delete(item) - del self._playerList[next(i for (i, d) in enumerate(self._playerList) if d['player']['id'] == item)] - self.save_list() + p_id = sel[0] + self.tree.delete(p_id) + del self.userPlayers[next(i for (i, player) in enumerate(self.userPlayers) if player.playerid == p_id)] + self.save_players_list() def _on_cell_edited(self, event): col, item = self.tree.get_event_info() values = self.tree.item(item, 'values') - for player in self._playerList: - if player['player']['id'] == item: - player['buy'] = int(values[2]) - player['sell'] = int(values[3]) - player['bin'] = int(values[4]) + for player in self.userPlayers: + if player.playerid == item: + player.maxBuy = int(values[2]) + player.sell = int(values[3]) + player.bin = int(values[4]) + player.enabled = str(values[5]) break - self.save_list() + self.save_players_list() def show_bid(self): - if len(self._playerList) > 0: - self.controller.show_frame(Bid, playerFile=self._playerFile, playerList=self._playerList) + if len(self.userPlayers) > 0: + # pass the list of only enabled players + enabledPlayers = list(filter(lambda x: x.enabled == 'yes', self.userPlayers)) + self.controller.show_frame(Bid, userPlayers=enabledPlayers) def show_watch(self): sel = self.tree.selection() if sel: - item = sel[0] - item = self._playerList[next(i for (i, d) in enumerate(self._playerList) if d['player']['id'] == item)] - self.controller.show_frame(Watch, player=item['player']) + p_id = sel[0] + player = next(filter(lambda x: x.playerid == p_id, self.userPlayers)) + self.controller.show_frame(Watch, player=player.details) + else: + from tkinter import messagebox + messagebox.showerror("Error","Select a player from the list first!") def kill_job(self): if self._job is not None: @@ -217,22 +244,16 @@ def active(self): if self.controller.api is None: self.controller.show_frame(Login) - # Backwards compatability - if isinstance(self._playerFile, list): - self._playerList = self._playerFile - self._playerFile = { - self.controller.user: self._playerList - } - self.save_list() - # Check if we have a list for this user - if self.controller.user not in self._playerFile: - self._playerFile[self.controller.user] = [] + if self.controller.user not in self.allPlayers: + self.allPlayers[self.controller.user] = [] - self._playerList = self._playerFile[self.controller.user] - for item in self._playerList: - self.add_player(item, write=False) + # Extract from the all players file only the players belonging to the current user + self.userPlayers = self.allPlayers[self.controller.user] + # Add each player to the list + for player in self.userPlayers: + self.add_player(player, write=False) from frames.login import Login from frames.watch import Watch