├── .gitignore ├── LICENSE ├── README.md ├── __version__ ├── _config.yml ├── bot.py ├── settings.json ├── steamguard.json ├── trade.csv └── whitelist.data /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.xml 3 | .idea/tf2-trade-bot.iml 4 | test.py -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Nathan Zilora 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :exclamation: ***USE AT YOUR OWN RISK*** :exclamation: 2 | **There are many known bugs that will never be fixed.** 3 | 4 | **If you can get the bot to work, congratulations. If you can't, too bad. If it stops working in the future, too bad. This code is no longer under development.** 5 | 6 | 7 | # the TF2 trade bot! 8 | Originally created by [Zwork101](https://github.com/Zwork101) 9 | 10 | This is a steam trade bot that trades TF2 items, automatically! Easy to setup, for non-developers! 11 | 12 | # Installation 13 | If you have "git" installed, you can just type 14 | ```git 15 | git clone https://github.com/Zwork101/tf2_trade_bot.git 16 | ``` 17 | otherwise, just download the zip. Make sure you keep every file in the same folder 18 | # Setup 19 | You will need the language python3.6 or greater. If you don't have python installed, you can find the download [here](https://www.python.org/). 20 | Each file has a description in this repo, to find out what you should do. You can ignore bot.py however, that doesn't need changing :P 21 | If you need a more in-depth setup guide, click [here](#in-depth-setup). 22 | 23 | # Running 24 | If you are here, you have added your ID and secrets to steamguard.json, password, username and api keys to settings.json, and filled out trade.data with whatever you wanted to sell or buy. You can now run bot.py! You *will* have to install some packages, but the bot will handle that part for you! Just restart the program when it tells you to. If you're having truoble with somthing or if you come across an error, make an issue in this repo and I'll respond asap! 25 | 26 | # Credits 27 | This bot was made completely by me, Zwork101. It is under the MIT license. I am not responsible for any loss you might receive whilst using this program. If you enjoyed this bot, steam item donations are accepted! 28 | 29 | ### With love, Zwork101 30 | 31 | # to-do list 32 | * ~~Add catch for when items become unavailable to trade~~ 33 | * Neaten and make code more efficient 34 | * ~~change current input to csv for trade.data~~ 35 | * add 2 seperate logs, one for debugging, one for trades 36 | * add ability to sync bot's trades with backpack.tf 37 | * add GUI? 38 | 39 | 40 | # In-depth setup 41 | You'll need python 3.6 to use this bot, download it [here](https://www.python.org/). 42 | When editing `.json` files, make sure to leave quotes in if they're there. Removing them will make everything crash and burn, possibly causing a threat to your life. 43 | 1. If you have git installed, use ```git clone https://github.com/Zwork101/tf2_trade_bot.git``` to download the bot in the folder you're in. Otherwise, download the zip file and extract it into a folder. 44 | 2. If you already have your shared secret and identity secret, go to step 3. Otherwise, go [here](https://github.com/Jessecar96/SteamDesktopAuthenticator) and follow the instructions. When it asks you for an encrytption key, make sure to leave it blank. Then, go to the file you installed it in and open the "maFiles" folder. Open the file with numbers in its name in a text editor. Find your shared_secret and identity_secret (eg "shared_secret":"bla" - your shared secret is bla). You can always do CTRL+F to search to file if you don't want to scroll along. 45 | 3. Open "steamguard.json" in a text editor. Where it says "steam64 id" (next to "steamid") paste in the 64-bit steam id of the account. If you can't find this, go [here](https://steamid.io/), paste in the profile URL of the account and copy the steamID64. Where it says "steam shared secret" paste your shared secret. Where it says "steam identity secret" paste your identity secret. Save the file and close it. 46 | 4. Open "settings.json" in a text editor. Where it says "steamAPI key" paste your steam API key (if you don't have one go [here](https://steamcommunity.com/dev/apikey) - it doesn't matter what you put as the domain). To the right of "username" paste your username (the one you use when signing in) where it says "username here", and where to the right of "password" paste your password. Next, paste your backpack.tf API key where it says "backpack.tf key". If you don't have one, get one [here](https://backpack.tf/developer/apikey/view) (again, the site URL doesn't matter. In the comments you should say it's for a trading bot). To the right of "accept_escrow" put a 1 if you want trades that would incur an escrow period to be accepted, and a 0 if you don't. To the left of "decline_trades", put a 0 if you want incorrect trades to be ignored, and a 1 if you want them to be declined. To the left of "confirm_options", put "trades" if you want only trades accepted by the bot to be confirmed and "all" if you want all trades to be confirmed. Please note - it is more secure to only confirm trades the bot has made. If you confirm all trades anyone who could access your account could take items from you (for example if you're on a shared computer where you stay signed in). Save this file and close it. 47 | 5. Open "whitelist.data" in a text editor. Delete everything in there, and put 64-bit steam ids in there separated by a comma. Trades from these people will be accepted no matter what the contents are. Save this file and close it. 48 | 6. Open "trade.csv" in a text editor. Delete the last two lines. In here, you can put the details of all the items you want to buy and sell. Each line is a new item to be bought or sold. First, write down the name of the item (if it has a comma in it's name, use $$ instead - so "Taunt: Rock, Paper, Scissors" would be "Taunt: Rock$$ Paper$$ Scissors"). Then write a comma. Now, you'll need to do some maths. There are 5 fields you need to fill - scrap, reclaimed, refined, keys and buds. It's easier to work this out from the right. For this example we'll say you want 1 key, 23.88 ref. Nobody uses buds, so we'll leave them out. You want one key for this item - so the key value is 1. There's 23 refined metal - so ref is 23. There's two reclaimed metal, so rec is 2, and there are two scrap metal so scrap is 2. Now, place all these numbers together with a . between each one. It's scrap.rec.ref.key.bud, so the value would be 2.2.23.1.0 . Easy, right? ("no", everyone says). Put another comma on the line and now you have to put whether you'll be buying or selling the item. For this example we'll be selling, so put sell. That's it! With our example (to sell a Taunt: Rock, Paper, Scissors for 1 key 23.88 ref), the finished line would be `Taunt: Rock$$ Paper$$ Scissors, 2.2.23.1.0, sell`. Do this for every item. When you're done, make sure to save the file and close it. 49 | 7. That's it! Feel free to run the bot by double-clicking "bot.py". The bot should guide you through the installation of some packages, but this is a one time process so won't happen every time you want to run the bot. 50 | 51 | Happy profit-making! 52 | -------------------------------------------------------------------------------- /__version__: -------------------------------------------------------------------------------- 1 | 1.10.18 2 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-merlot -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import time 5 | from distutils.version import LooseVersion 6 | import importlib 7 | import pip 8 | from enum import Enum 9 | import logging 10 | import csv 11 | import subprocess 12 | 13 | try: 14 | main = pip.main 15 | except AttributeError: 16 | # module 'pip' has no attribute 'main' 17 | from pip._internal import main 18 | apikey = '' 19 | password = '' 20 | username = '' 21 | bkey = '' 22 | buy_trades = {} 23 | sell_trades = {} 24 | items = {} 25 | key_price = 0 26 | bud_price = 0 27 | escrow = None 28 | whitelist = [] 29 | currencies = {'bud':'Earbuds', 'ref':'Refined Metal', 'rec':'Reclaimed Metal', 'scrap':'Scrap Metal', 'key':'Mann Co. Supply Crate Key'} 30 | packages = ['steampy', 'requests'] 31 | declined_trades = None 32 | past_time = time.time() 33 | start_time = time.time() 34 | 35 | logging.basicConfig(filename='trade.log', level=logging.DEBUG, 36 | format='[%(asctime)s][%(levelname)s][%(name)s]: %(message)s', datefmt='%m/%d/%Y %I:%M:%S %p') 37 | 38 | start_text = """ 39 | _____ _____ ____ _____ ____ _ ____ U _____ u ____ U ___ u _____ 40 | |_ " _| |" ___||___"\ |_ " _|U | _"\ u U /"\ u | _"\ \| ___"|/ U | __")u \/"_ \/|_ " _| 41 | | | U| |_ uU __) | | | \| |_) |/ \/ _ \/ /| | | | | _|" \| _ \/ | | | | | | 42 | /| |\ \| _|/ \/ __/ \ /| |\ | _ < / ___ \ U| |_| |\| |___ | |_) |.-,_| |_| | /| |\ 43 | u |_|U |_| |_____|u u |_|U |_| \_\ /_/ \_\ |____/ u|_____| |____/ \_)-\___/ u |_|U 44 | _// \\\_ )(\\\,- << // _// \\\_ // \\\_ \\\ >> |||_ << >> _|| \\\_ \\\ _// \\\_ 45 | (__) (__)(__)(_/(__)(__) (__) (__)(__) (__)(__) (__)(__)_) (__) (__) (__) (__) (__) (__) (__) 46 | 47 | Created by: Zwork101 Github: https://github.com/Zwork101 Steam: https://steamcommunity.com/id/ZWORK101 48 | 49 | THIS VERSION IS NO LONGER UNDER DEVELOPMENT AND BUGS WILL NOT BE FIXED. IT IS HIGHLY RECOMMENDED TO SWITCH 50 | TO THE NEW VERSION. YOU CAN FIND THIS AT: https://github.com/mninc/tf2-trade-bot-2\n 51 | """ 52 | 53 | class TradeOfferStatus(Enum): 54 | INVALID = 1 55 | ACTIVE = 2 56 | ACCEPTED = 3 57 | EXPIRED = 4 58 | CANCELED = 6 59 | INVALID_ITEMS = 8 60 | WAIT_CONF = 9 61 | WAIT_SFAC = 10 62 | ESCROW = 11 63 | 64 | 65 | class TradeManager: 66 | 67 | """ 68 | The manager for trades. This will be used to organize trades and keep everything from falling apart. 69 | Prams: client (steampy.client.SteamClient object) and conf (steampy.confirmation.ConfirmationExecutor) 70 | Public values: client and conf (see above) 71 | Public functions: accept, check_trades_content, get_new_trades, check_good_trades, check_bad_trades 72 | """ 73 | 74 | def __init__(self, client, conf): 75 | self._trades = [] 76 | self._pending_trades = [] 77 | self._try_confs = [] 78 | self._declined_trades = [] 79 | self.client = client 80 | self.conf = conf 81 | 82 | 83 | def decline(self, trade): 84 | if decline_trades: 85 | self.client.decline_trade_offer(trade.id) 86 | if trade.id not in self._declined_trades: 87 | self._declined_trades.append(trade.id) 88 | 89 | 90 | def accept(self, trade): 91 | """ 92 | The accept function handles accepting trades. This is important, because different errors could occur. 93 | Prams: (self), trade (Trade object) 94 | Output: None 95 | """ 96 | try: 97 | self.client.accept_trade_offer(trade.id) 98 | return True 99 | except BaseException as BE: 100 | if BE.__class__ == KeyError: 101 | print(f'ERROR: Issue confirming trade: {trade.id}, trying again') 102 | #self._trades.remove(trade) 103 | self._pending_trades.append(trade) 104 | logging.warning(f'TRADE ACCEPT ERROR: {type(BE).__name__}: {BE}') 105 | return False 106 | 107 | 108 | def check_trades_content(self): 109 | """ 110 | This will check the current trades in self._pending_trades and decide if they are correct or not 111 | Then it will move the good trades to self._declined_trades and self._trades after acccepting/declining 112 | trade offers. 113 | Prams: (self) 114 | Output: None 115 | """ 116 | for trade in range(len(self._pending_trades)-1,-1,-1): 117 | trade = self._pending_trades[trade] 118 | sell_value = 0 119 | buy_value = 0 120 | extra_sell = [] 121 | extra_buy = [] 122 | if not trade.items_to_give: 123 | self._pending_trades.remove(trade) 124 | self._trades.append(trade) 125 | self.accept(trade) 126 | continue 127 | exit_trade = False 128 | for item in trade.items_to_give: 129 | if not exit_trade: 130 | if item not in sell_trades: 131 | if item in currencies.values(): 132 | extra_sell.append(item) 133 | else: 134 | print('[TRADE]: Unknown item we\'re giving, declining') 135 | self.decline(trade) 136 | self._pending_trades.remove(trade) 137 | logging.info("DECLINING TRADE WITH UN-KNOWN ITEM") 138 | exit_trade = True 139 | else: 140 | sell_value = add_values(float(sell_trades[item]), float(sell_value)) 141 | 142 | if exit_trade: 143 | continue 144 | 145 | for item in trade.items_to_receive: 146 | if item in buy_trades: 147 | buy_value = add_values(float(buy_trades[item]), float(buy_value)) 148 | elif item in currencies.values(): 149 | extra_buy.append(item) 150 | 151 | sell_curr = sort(extra_sell) 152 | buy_curr = sort(extra_buy) 153 | sell_value += calculate(sell_curr[0], sell_curr[1], sell_curr[2], sell_curr[3], sell_curr[4]) 154 | buy_value += calculate(buy_curr[0], buy_curr[1], buy_curr[2], buy_curr[3], buy_curr[4]) 155 | 156 | if sell_value <= buy_value: 157 | print(f'[TRADE]: Looks good! They gave us:\n{str(trade.items_to_receive)}') 158 | print(f'[TRADE]: We gave them:\n{str(trade.items_to_give)}') 159 | print('[TRADE]: Attempting to accept offer') 160 | try: 161 | logging.info(f"ATTEMPTING TRADE: {trade.id}\nSELL: {sell_value} BUY:{buy_value}\n{trade.trade}") 162 | self._trades.append(trade) 163 | self._pending_trades.remove(trade) 164 | self.accept(trade) 165 | except ConfirmationExpected: 166 | logging.warning(f'FAILED TO CONFIRM TRADE: {trade.id} (FIRST TRY)') 167 | self._try_confs.append(trade.id) 168 | else: 169 | print(f'[TRADE]: No good! They offered us:\n{str(trade.items_to_receive)}') 170 | print(f'[TRADE]: For our:\n{str(trade.items_to_give)}') 171 | print('[TRADE]: Declining offer') 172 | logging.info(f"DECLINING INVALID TRADE: {trade.id}\nSELL: {sell_value} BUY:{buy_value}\n{trade.trade}") 173 | self.decline(trade) 174 | self._pending_trades.remove(trade) 175 | 176 | 177 | def get_new_trades(self): 178 | """ 179 | Collects new trades, will compare them to current trades to ensure they are new. Accepts if the sender 180 | is whitelisted, delcines if the user is a scammer or escrow. If not, moved it to 181 | self._pending_trades (list) 182 | Prams: (self) 183 | Output: None 184 | """ 185 | new_trades = client.get_trade_offers()['response'] 186 | #logging.debug(new_trades) 187 | for new_trade in new_trades['trade_offers_received']: 188 | if (not new_trade['tradeofferid'] in [t.id for t in self._trades]) \ 189 | or (new_trade['tradeofferid'] in self._declined_trades): 190 | id64 = 76561197960265728 + new_trade['accountid_other'] 191 | trade = Trade(new_trade, id64) 192 | logging.info(f"FOUND NEW TRADE: {trade.id}") 193 | if str(id64) in whitelist: 194 | print(f"[WHITELIST]: Neat! The user sending this trade is whitelisted! Attempting confirmation (STEAM ID:{id64})") 195 | logging.info(f'TRADE WHITELISTED ATTEMPTING TRADE: {trade.id}') 196 | self.accept(trade) 197 | self._trades.append(trade) 198 | continue 199 | print(f'[TRADE]: Found trade (ID: {trade.id})') 200 | if self._check_partner(trade): 201 | if not accept_escrow and trade.escrow: 202 | print("[TRADE]: Trade is escrow, declining") 203 | logging.info(f'DECLINING ESCROW TRADE: {trade.trade}') 204 | self.decline(trade) 205 | else: 206 | self._pending_trades.append(trade) 207 | 208 | 209 | def _check_partner(self, trade): 210 | """ 211 | To check if the user is a scammer from backpack.tf and steamrep. This uses the backpack.tf API. 212 | The API will supply the steamrep stats for the user. If the user is a scammer, it 213 | will decline the trade and move it to self._declined_trades. 214 | Prams: (self), trade (Trade object) 215 | Output: None 216 | """ 217 | print("[TRADE]: Checking for trade bans on backpack.tf and steamrep.com") 218 | rJson = requests.get(f"https://backpack.tf/api/users/info/v1?", 219 | data={'key':bkey, 'steamids':trade.other_steamid}).json() 220 | 221 | logging.debug(str(rJson)) 222 | if "bans" in rJson['users'][trade.other_steamid].keys(): 223 | if "steamrep_caution" in rJson['users'][trade.other_steamid]['bans'] or \ 224 | "steamrep_scammer" in rJson['users'][trade.other_steamid]['bans']: 225 | print("[steamrep.com]: SCAMMER") 226 | print('[TRADE]: Ending trade...') 227 | logging.info(f"DECLINED SCAMMER (ID:{trade.other_steamid})") 228 | self.decline(trade) 229 | return False 230 | 231 | print('[steamrep.com]: User is not banned') 232 | if "all" in rJson['users'][trade.other_steamid]['bans']: 233 | print('[backpack.tf]: SCAMMER') 234 | print('[TRADE]: Ending trade...') 235 | logging.info(f"DECLINED SCAMMER (ID:{trade.other_steamid})") 236 | self.decline(trade) 237 | return False 238 | print('[backpack.tf]: User is clean') 239 | print("[backpack.tf/steamrep.com]: User is clean") 240 | return True 241 | 242 | 243 | def check_bad_trades(self): 244 | """ 245 | Looks at the current trades in self._trades and checks if the trade has become invalid (eg 246 | if the trade was cancled). It will remove it from trades and report what happened to the user 247 | Prams: (self) 248 | Output: None 249 | """ 250 | for trade_index in range(len(self._trades)-1, -1, -1): 251 | trade = self._trades[trade_index] 252 | status = trade.status() 253 | if status == TradeOfferStatus.INVALID.value: 254 | print(f'[ERROR]: Trade offer id {trade.id} seems to be invalid') 255 | self._trades.remove(trade) 256 | logging.warning(f'TRADE {trade.id} BECAME invalid') 257 | elif status == TradeOfferStatus.CANCELED.value: 258 | print(f'[TRADE]: Trade {trade.id} was canceled.') 259 | self._trades.remove(trade) 260 | logging.warning(f'TRADE {trade.id} BECAME canceled') 261 | elif status == TradeOfferStatus.EXPIRED.value: 262 | print(f'[TRADE]: Trade {trade.id} has expired... How did that happen?') 263 | self._trades.remove(trade) 264 | logging.warning(f'TRADE {trade.id} BECAME expired') 265 | elif status == TradeOfferStatus.INVALID_ITEMS.value: 266 | print(f'[TRADE]: Items attempting to trade became invalid. {trade.id}') 267 | self._trades.remove(trade) 268 | logging.warning(f'TRADE {trade.id} BECAME invalid_items') 269 | elif status == TradeOfferStatus.ESCROW.value and not accept_escrow: 270 | print('[ERROR]: Whoops, escrow trade was confirmed. Sorry about that') 271 | self._trades.remove(trade) 272 | logging.fatal(f'ACCEPTED ESCROW TRADE') 273 | 274 | def check_good_trades(self): 275 | """ 276 | This method does 2 things. The first thing it does is check to see if trades have been accepted. 277 | If they have, they will be removed from self._trades and will report that the trade was accepted. 278 | The second thing is to try and confirm trades that are having issues confirming. If it was confirmed, 279 | it will be removed from self._try_confs, and report to user it was confirmed. 280 | Prams: (self) 281 | Output: None 282 | """ 283 | for trade_index in range(len(self._trades) - 1, -1, -1): 284 | trade = self._trades[trade_index] 285 | status = trade.status() 286 | if status == TradeOfferStatus.ACCEPTED.value: 287 | print(f'[TRADE]: Accepted trade {trade.id}') 288 | self._trades.remove(trade) 289 | logging.info(f'TRADE {trade.id} WAS ACCEPTED') 290 | 291 | 292 | def confirm_check(self): 293 | if confirm_settings == 'all': 294 | logging.debug('ACCEPTING EVERYTHING') 295 | for confirmation in self.conf._get_confirmations(): 296 | self.conf._send_confirmation(confirmation) 297 | logging.info(f'SENT CONFIRMATION FOR CONF WITH ID OF {confirmation.id}') 298 | elif confirm_settings == 'trade': 299 | for tradeid in self._try_confs: 300 | try: 301 | self.conf.send_trade_allow_request(tradeid) 302 | print(f'[TRADE]: Accepted trade {tradeid}') 303 | logging.info(f'TRADE {tradeid} WAS ACCEPTED (after manual confirmation)') 304 | self._try_confs.remove(tradeid) 305 | except ConfirmationExpected: 306 | logging.debug(f'CONFIRMATION FAILED ON {tradeid}') 307 | 308 | 309 | class Trade: 310 | 311 | """ 312 | This is an object mainly to store data about a trade, and make it easy to access. It can also 313 | the currency in the trade and fetch the status of the trade. 314 | Prams: trade_json (dict), other_steamid (str) 315 | Public values: self.trade (dict), self.escrow (int), self.items_to_give (list), self.items_to_receive (list), 316 | self.id (int/str), self.other_steamid (str) 317 | Public functions: sort, status 318 | """ 319 | 320 | def __init__(self, trade_json:dict, other_steamid:int): 321 | self.trade = trade_json 322 | self.escrow = int(trade_json['escrow_end_date']) 323 | self.items_to_give = self._items_to_give() 324 | self.items_to_receive = self._items_to_receive() 325 | self.id = trade_json["tradeofferid"] 326 | self.other_steamid = str(other_steamid) 327 | 328 | def _items_to_receive(self): 329 | """ 330 | Adds all items to self.items_to_receive as their market name. Should only be used in initialization. 331 | Prams: (self) 332 | Output: item_names (list) 333 | """ 334 | item_names = [] 335 | for assetID in self.trade['items_to_receive']: 336 | item_names.append(self.trade['items_to_receive'][assetID]['market_name']) 337 | return item_names 338 | 339 | def _items_to_give(self): 340 | """ 341 | Adds all items to self.items_to_give as their market name. Should only be used in initialization. 342 | Prams: (self) 343 | Output: item_names (list) 344 | """ 345 | item_names = [] 346 | for assetID in self.trade['items_to_give']: 347 | item_names.append(self.trade['items_to_give'][assetID]['market_name']) 348 | return item_names 349 | 350 | def sort(self, typ): 351 | """ 352 | Counts the amount of a currency type there is in one side of the trade. "sort" is sort 353 | of misleading (see what I did there), it just counts how many scraps, recs, ref, keys and 354 | buds there are. 355 | Prams: (self), type (str) 356 | Output: curr (list) 357 | """ 358 | if typ == 'sell': 359 | return sort(self.items_to_receive) 360 | else: 361 | return sort(self.items_to_give) 362 | 363 | def status(self): 364 | """ 365 | Fetches the status of the trade from steam. This way we can get live data. 366 | Prams: (self) 367 | Output: trade_json['trade_offer_state'] (int/str) 368 | """ 369 | try: 370 | trade_json = client.get_trade_offer(self.id)['response']['offer'] 371 | except KeyError: 372 | #If key error, the trade doesn't exist anymore. If so, it's invalid 373 | trade_json = {'trade_offer_state':1} 374 | return trade_json['trade_offer_state'] 375 | 376 | def add_values(v1, v2): 377 | v1_rem, v2_rem = int(str(v1).split('.')[1]), int(str(v2).split('.')[1]) 378 | ref = int(v1) + int(v2) 379 | 380 | v1_rec, v2_rec = v1_rem // 33, v2_rem // 33 381 | v1_rem, v2_rem = v1_rem - v1_rec * 33, v2_rem - v2_rec * 33 382 | 383 | srp_added = v1_rem + v2_rem 384 | v1_rec += srp_added // 33 385 | srp_added -= (srp_added // 33) * 33 386 | 387 | rec_added = v1_rec + v2_rec 388 | ref += rec_added // 3 389 | rec_added -= (rec_added // 3) * 3 390 | 391 | return float(str(ref) + '.' + str(rec_added*33 + srp_added)) 392 | 393 | def sort(items:list): 394 | curr = [0,0,0,0,0] 395 | for item in items: 396 | if item == currencies['scrap']: 397 | curr[0] += 1 398 | elif item == currencies['rec']: 399 | curr[1] += 1 400 | elif item == currencies['ref']: 401 | curr[2] += 1 402 | elif item == currencies['key']: 403 | curr[3] += 1 404 | elif item == currencies['bud']: 405 | curr[4] += 1 406 | return curr 407 | 408 | def check_for_updates(): 409 | with open('__version__', 'r') as file: 410 | curr_version = file.read() 411 | r = requests.get('https://raw.githubusercontent.com/Zwork101/tf2-trade-bot/master/__version__') 412 | new_version = r.text 413 | if LooseVersion(new_version) > LooseVersion(curr_version): 414 | print('[PROGRAM]: A new version is available, would you like to install?') 415 | yn = input('[y/n]: ') 416 | if yn[0].lower() == 'y': 417 | print('[Installer]: Starting installation...', end='') 418 | bot_update = requests.get('https://raw.githubusercontent.com/Zwork101/tf2-trade-bot/master/bot.py') 419 | with open('__version__', 'w') as file: 420 | file.write(new_version) 421 | print('.', end='') 422 | with open('bot.py', 'w') as file: 423 | file.write(bot_update.text) 424 | print('.') 425 | print('Update complete! Restart now.') 426 | input('press enter to close program...\n') 427 | os._exit(0) 428 | 429 | 430 | def calculate(scrapC, recC, refC, keyC, budC): 431 | #For each currency, add it using add_values function 432 | total_value = 0.0 433 | for scrap in range(scrapC): 434 | total_value = add_values(total_value, .11) 435 | for rec in range(recC): 436 | total_value = add_values(total_value, .33) 437 | for ref in range(refC): 438 | total_value = add_values(total_value, 1.0) 439 | for key in range(keyC): 440 | total_value = add_values(total_value, float(key_price)) 441 | for bud in range(budC): 442 | total_value = add_values(total_value, float(bud_price)) 443 | return total_value 444 | 445 | def check_install(pkg, c, imp=''): 446 | try: 447 | importlib.import_module(pkg) 448 | print(f'[PROGRAM]: Required package is installed {c}/{len(packages)}') 449 | logging.debug(f"MODULE {pkg} IS INSTALLED") 450 | except: 451 | logging.info(f"MODULE {pkg} IS NOT INSTALLED") 452 | if imp: 453 | pkg = imp 454 | print('[PROGRAM]: A required package is not installed, installing...') 455 | main(['install', pkg]) 456 | print('[PROGRAM]: Installed package! Please restart this program to continue.') 457 | input('press enter to close program...\n') 458 | os._exit(0) 459 | 460 | # def check_trade(trade_obj, items_value, typ): 461 | # curr = trade_obj.sort(typ) 462 | # value = calculate(curr[0], curr[1], curr[2], curr[3], curr[4]) 463 | # if typ == 'sell': 464 | # b_curr = trade_obj.sort('buy') 465 | # items_value += calculate(b_curr[0], b_curr[1], b_curr[2], b_curr[3], b_curr[4]) 466 | # else: 467 | # s_curr = trade_obj.sort('sell') 468 | # items_value += calculate(s_curr[0], s_curr[1], s_curr[2], s_curr[3], s_curr[4]) 469 | # 470 | # logging.debug(f"TRADE {trade_obj.id} is a {typ} trade, and is worth {value}, with items being {items_value}") 471 | # if typ == 'sell': 472 | # if value >= items_value: 473 | # return True 474 | # else: 475 | # return False 476 | # else: 477 | # if value <= items_value: 478 | # return True 479 | # else: 480 | # return False 481 | 482 | def heartbeat(): 483 | global past_time 484 | print(f"[HEARTBEAT]: ~{90 - int(time.time() - past_time)} seconds until next heartbeat") 485 | if int(time.time() - past_time) >= 90: 486 | p = requests.post(f"https://backpack.tf/api/aux/heartbeat/v1?", data={"token": token, "automatic": "all"}) 487 | if p.status_code != 200: 488 | print(f'[HEARTBEAT]: Error when sending heartbeat: {p.json()["message"]}') 489 | logging.warning(f"ERROR SENDING HEARTBEAT: {p.json()['message']}") 490 | else: 491 | print("[HEARTBEAT]: Sent heartbeat to backpack.tf") 492 | logging.info("HEARTBEAT SENT") 493 | past_time = time.time() 494 | 495 | if __name__ == '__main__': 496 | print(start_text) 497 | 498 | for pkg in packages: 499 | check_install(pkg, packages.index(pkg)+1, '' if pkg!='backpackpy' else 'backpack.py') 500 | 501 | from steampy.client import SteamClient 502 | from steampy import confirmation 503 | from steampy.exceptions import InvalidCredentials, ConfirmationExpected 504 | #from backpackpy import listings 505 | import requests 506 | 507 | check_for_updates() 508 | 509 | try: 510 | with open('settings.json', 'r') as cfg: 511 | try: 512 | data = json.load(cfg) 513 | try: 514 | apikey, password, username, bkey, accept_escrow = data['apikey'],\ 515 | data['password'], data['username'], data['bkey'], data['accept_escrow'] 516 | token = requests.get(f"https://backpack.tf/api/aux/token/v1?key={bkey}").json()['token'] 517 | decline_trades = data.get('decline_trades', 1) 518 | confirm_settings = data.get('confirm_options', 'trades') 519 | except KeyError as k: 520 | logging.warning(f'SETTINGS FILE MISSING {k} VALUE') 521 | print(f'[settings.json]: Whoops! You are missing the {k} value') 522 | input('Press enter to close program...\n') 523 | os._exit(1) 524 | except json.JSONDecodeError: 525 | logging.warning('INVALID SETTINGS FILE') 526 | print('[PROGRAM]: Whoops! It would seem that you settings.json file is invalid!') 527 | input('press enter to close program...\n') 528 | os._exit(1) 529 | logging.debug("LOADED SETTINGS") 530 | 531 | except FileNotFoundError: 532 | logging.warning("SETTINGS NOT FOUND, CREATING") 533 | print('[PROGRAM]: File settings.json not found! Would you like to make one?') 534 | yn = input('[y/n]: ') 535 | if yn[0].lower() == 'y': 536 | apikey = input('[settings.json]: Enter your steam API key. (https://steamcommunity.com/dev/apikey)\n') 537 | password = input('[settings.json]: Enter your password. \n') 538 | username = input('[settings.json]: Enter your username. \n') 539 | bkey = input('[settings.json]: Enter your backpack.tf API key. (https://backpack.tf/api/register)\n') 540 | accept_escrow = input('[settings.json]: Accept escrow trades? (0 for no, 1 for yes)\n') 541 | print('[PROGRAM]: Writing data to file...') 542 | with open('settings.json', 'w') as file: 543 | json.dump({'apikey':apikey, 'password':password, 'username':username, 'bkey':bkey, 544 | "accept_escrow":accept_escrow}, file) 545 | print('[PROGRAM]: Wrote to file') 546 | else: 547 | print("[PROGRAM]: Can't run without user information.") 548 | input('Press enter to close program...\n') 549 | os._exit(1) 550 | 551 | client = SteamClient(apikey) 552 | conf = None 553 | 554 | print('[PROGRAM]: Obtaining bud and key values from backpack.tf...') 555 | rJson = requests.get(f'https://backpack.tf/api/IGetCurrencies/v1?key={bkey}').json()['response'] 556 | logging.debug(f"KEY VALUE RESPONSE: {rJson}") 557 | if rJson['success']: 558 | key_price = rJson['currencies']['keys']['price']['value'] 559 | bud_price = rJson['currencies']['earbuds']['price']['value'] 560 | print(f'[PROGRAM]: Obtained values! KEY <{key_price} ref>, BUD <{bud_price} keys>.') 561 | logging.debug("OBTAINED KEY AND BUD VALUES") 562 | else: 563 | logging.fatal("FAILED TO OBTAIN KEY AND BUG VALUES") 564 | print(f'[backpack.tf]: {rJson["message"]}') 565 | input('Press enter to close program...\n') 566 | os._exit(1) 567 | 568 | try: 569 | client.login(username, password, 'steamguard.json') 570 | except json.decoder.JSONDecodeError: 571 | logging.warning("STEAMGUARD FILE INVALID") 572 | print('[steamguard.json]: Unable to read file.') 573 | input('Press enter to close program...\n') 574 | os._exit(1) 575 | except FileNotFoundError: 576 | logging.warning("UNABLE TO FIND STEAMGAURD FILE") 577 | print('[steamguard.json]: Unable to find file.') 578 | input('Press enter to close program...\n') 579 | os._exit(1) 580 | except InvalidCredentials: 581 | logging.info("CREDENTIALS INVALID") 582 | print('[PROGRAM]: Your username, password, ID and/or secrets are invalid.') 583 | input('Press enter to close program...\n') 584 | os._exit(1) 585 | else: 586 | conf = confirmation.ConfirmationExecutor( 587 | client.steam_guard['identity_secret'], 588 | client.steam_guard['steamid'], 589 | client._session) 590 | logging.info("CREATED CLIENT AND CONFIRMATION MANAGER") 591 | 592 | print(f'[PROGRAM]: Connected to steam! Logged in as {username}') 593 | try: 594 | with open('trade.csv', 'r') as file: 595 | reader = csv.DictReader(file) 596 | count = 1 597 | fails = [] 598 | for row in reader: 599 | count += 1 600 | try: 601 | if row['type'].strip()[0].lower() == 's': 602 | p = row['price'].split('.') 603 | p = [int(i) for i in p] 604 | price = calculate(p[0], p[1], p[2], p[3], p[4]) 605 | sell_trades[row['item_name'].strip().replace("$$", ",")] = price 606 | elif row['type'].strip()[0].lower() == 'b': 607 | p = row['price'].split('.') 608 | p = [int(i) for i in p] 609 | price = calculate(p[0], p[1], p[2], p[3], p[4]) 610 | buy_trades[row['item_name'].strip().replace("$$", ",")] = price 611 | except AttributeError: 612 | fails.append(count) 613 | logging.info(f'LOADED TRADE DATA: BUY: {buy_trades} SELL: {sell_trades}') 614 | except FileNotFoundError: 615 | logging.warning("TRADE FILE NOT FOUND") 616 | print('[trade.data]: Unable to find file.') 617 | input('Press enter to close program...\n') 618 | os._exit(1) 619 | print(f'[CSV]: Failed to load these lines: {fails}') 620 | print('[PROGRAM]: Finished loading trading data.') 621 | # yn = input("Would you like to sync to backpack.tf listings?\n[y/n]: ") 622 | # if yn[0].lower() == 'y': 623 | # steamid = client.steam_guard['steamid'] 624 | # steam_inv = requests.get(f'http://steamcommunity.com/inventory/{steamid}/440/2?l=english&count=5000').json() 625 | # bp_listings = requests.get("https://backpack.tf/api/classifieds/listings/v1?", data={'token':token}).json() 626 | # class_id = False 627 | # for classified in bp_listings["listings"]: 628 | # asset_id = classified['id'] 629 | # for item in steam_inv['assets']: 630 | # if item['assetid'] == classified['id']: 631 | # class_id = item['classid'] 632 | # if class_id: 633 | # for item in steam_inv['descriptions']: 634 | # if item['classid'] == class_id: 635 | # market_name = item['market_name'] 636 | # market_type = classified['intent'] 637 | # ref, keys = classified['currencies']['metal'], classified['currencies']['keys'] 638 | # sep = str(ref).split('.') 639 | # if len(sep) == 2: 640 | # price = calculate(int(sep[0])/11, 0, int(sep[0]), keys, 0) 641 | # else: 642 | # price = calculate(0, 0, int(ref), keys, 0) 643 | # if market_type: 644 | # sell_trades[market_name] = price 645 | # else: 646 | # buy_trades[market_name] = price 647 | # print(buy_trades) 648 | # print(sell_trades) 649 | # os._exit(0) 650 | 651 | try: 652 | with open('whitelist.data', 'r') as file: 653 | steam_ids = file.read() 654 | if steam_ids: 655 | for steam_id in steam_ids.split(','): 656 | whitelist.append(steam_id) 657 | print(f'[WHITELIST]: Whitelist created with the following ids: {whitelist}') 658 | logging.info(f"LOADED WHITELIST: {whitelist}") 659 | except FileNotFoundError: 660 | logging.debug("WHITELIST NOT FOUND") 661 | 662 | print('[PROGRAM]: Everything ready, starting trading.') 663 | print('[PROGRAM]: Press Ctrl+C to close at any time.') 664 | 665 | manager = TradeManager(client, conf) 666 | 667 | while True: 668 | if time.time() - start_time >= 3600: 669 | pass 670 | #subprocess.call(["python", os.path.join(sys.path[0], __file__)] + sys.argv[1:]) 671 | try: 672 | heartbeat() 673 | try: 674 | manager.get_new_trades() 675 | print('[TRADE-MANAGER] STEP 1 (get new trades) COMPLETE') 676 | logging.debug("(STEP 1 COMPLETE)") 677 | except json.decoder.JSONDecodeError: 678 | print("[PROGRAM]: Unexpected error, taking a break (10 seconds).") 679 | time.sleep(10) 680 | print('Starting again...') 681 | continue 682 | 683 | manager.check_trades_content() 684 | print('[TRADE-MANAGER]: STEP 2 (check new trades) COMPLETE') 685 | logging.debug("(STEP 2 COMPLETE)") 686 | manager.check_bad_trades() 687 | print('[TRADE-MANAGER]: STEP 3 (check for trades gone bad) COMPLETE') 688 | logging.debug("(STEP 3 COMPLETE)") 689 | manager.check_good_trades() 690 | print('[TRADE-MANAGER]: STEP 4 (check for successful trades) COMPLETE') 691 | logging.debug("(STEP 4 COMPLETE)") 692 | manager.confirm_check() 693 | print('[TRADE-MANAGER]: STEP 5 (check confirmations) COMPLETE') 694 | logging.debug("(STEP 5 COMPLETE)") 695 | print('[PROGRAM]: Cooling down... (10)') 696 | 697 | except InterruptedError: 698 | os._exit(0) 699 | 700 | except BaseException as BE: 701 | print(f'[ERROR]: {type(BE).__name__}: {BE}') 702 | logging.warning(f"UNEXPECTED ERROR: {type(BE).__name__}: {BE}") 703 | 704 | time.sleep(10) 705 | -------------------------------------------------------------------------------- /settings.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "apikey": "steamAPI key", 4 | "password": "password here", 5 | "username": "username here", 6 | "bkey":"backpack.tf key", 7 | "accept_escrow":0, 8 | "decline_trades":1, 9 | "confirm_options":"trades" 10 | } 11 | -------------------------------------------------------------------------------- /steamguard.json: -------------------------------------------------------------------------------- 1 | { 2 | "steamid": "steam64 id", 3 | "shared_secret": "steam shared secret", 4 | "identity_secret": "steam identity secret" 5 | } 6 | -------------------------------------------------------------------------------- /trade.csv: -------------------------------------------------------------------------------- 1 | item_name,price,type 2 | # Delete this line and ones that follow for your trades. DO NOT DELETE FIRST LINE. 3 | # A trade should look like this: (market name of item), (price {see below}), (type: buy/sell MUST BE EXACTLY buy or sell) 4 | # the price, should be formated like this -> scrap.rec.ref.key.bud - If not supplying some of those, give 0. Give values in amount 5 | # for example, 1 key and 7.55 ref is 2.1.7.1.0 6 | # This is what MY trade.csv file looks like :wink: 7 | Genuine Ham Shank, 1.1.2.0.0, buy 8 | Genuine Ham Shank, 2.1.2.0.0, sell 9 | -------------------------------------------------------------------------------- /whitelist.data: -------------------------------------------------------------------------------- 1 | STEAMID,STEAMID,STEAMID <- Delete this, just follow it's formatting 2 | --------------------------------------------------------------------------------