-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbuy_listener.py
446 lines (383 loc) · 17.9 KB
/
buy_listener.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
import better_exceptions
better_exceptions.MAX_LENGTH = None
import pika
import dotenv
import os
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.options import Options
import undetected_chromedriver as uc
import selenium.webdriver.chrome as chromedriver
from selenium import webdriver
import discord_webhook
import time as time
import json
import random
import logging as pylogger
from loguru import logger as logger
global platform
global driver
driver = None
global STEAM_USERNAME
STEAM_USERNAME = None
# ------------- #
global LAST_TIME
LAST_TIME = -1
global DELAY_RANGE
DELAY_RANGE = 15
# ------------- #
global PROFIT, ITEMS_BOUGHT, ITEMS_MISSED
PROFIT = 0
ITEMS_BOUGHT = 0
ITEMS_MISSED = 0
global GUI_OBJECTS
GUI_OBJECTS = {}
from sys import platform
if platform == "linux" or platform == "linux2":
# linux
OS = "linux"
elif platform == "darwin":
# OS X
OS = "mac"
elif platform == "win32":
# Windows...
OS = "windows"
print(f"platform is {OS}")
def buy_item_by_id(item_id, item_price, item_hash_name):
bot_inventory = driver.find_element(By.CLASS_NAME, "market-items")
item_to_buy = bot_inventory.find_elements(By.XPATH, f"//div[@data-id={item_id}]")
if len(item_to_buy) == 0:
logger.info(f"({item_hash_name}) - Cannot find this item in bot's inventory.")
return {
"success": False,
"message": "There is no such item in bot's inventory"
}
else:
wagered_balance = float(driver.find_element(By.ID, "withdraw-bal").text)
if wagered_balance < item_price: return {
"success": False,
"message": f"You do not have enough wagered balance for this operation. [wagered balance: ${wagered_balance}, price: ${item_price}]."
}
logger.info(f"({item_hash_name}) - Adding to cart.")
item_to_buy[0].click()
driver.find_element(By.ID, "buyItems").click()
# TODO: we shouldn't assume the purchase was successful
return {
"success": True,
"message": f"bought item {item_hash_name} for ${item_price}."
}
#global ITEMS
#ITEMS = []
discord_webhook_link = os.getenv("WEBHOOK_LINK")
webhook = discord_webhook.DiscordWebhook(url=discord_webhook_link)
CHECK_LIST_SIZE = 80
checked_list = []
def callback(ch, method, properties, body):
global DELAY_RANGE, PROFIT, ITEMS_BOUGHT, ITEMS_MISSED
# logger.debug("TIME UNTIL NEXT BUY: " + str(time.time() - LAST_TIME) + " seconds")
logger.debug(" [x] Received %r" % body)
message_dict = json.loads(body)
item_hash_name = message_dict['item_hash_name']
item_id = message_dict['item_id']
# if item_id in checked_list: return
# keep checked_list at 80 items
checked_list.append(item_id)
if len(checked_list) >= CHECK_LIST_SIZE:
checked_list.pop(0)
buy_prices = message_dict['buy_prices']
buy_usd = buy_prices['usd']
buy_keys = buy_prices['keys']
buy_refs = buy_prices['refs']
sell_prices = message_dict['sell_prices']
buyer_id = sell_prices['steamid']
sell_usd = sell_prices['usd']
sell_keys = sell_prices['keys']
sell_refs = sell_prices['refs']
logger.warning(f"({item_hash_name}) - Attempting to buy for ${buy_usd}.")
# calculate profit
profit = sell_usd - buy_usd
#global ITEMS
# base case: if list is empty
# if not len(ITEMS):
# ITEMS.append({
# 'profit': profit,
# 'item': message_dict,
# })
# logger.debug(f"({item_hash_name}) - Profit: ${profit}.")
#
# # insert sorted by profit
# for index, item in enumerate(ITEMS):
# if item['profit'] < profit:
# ITEMS.insert(index, {
# 'profit': profit,
# 'item': message_dict,
# })
# break
# if we've elapsed the cooldown time, buy the best item.
# TODO: what if this item is already gone by the time we finish our cooldown? @Osc44r
# this is a FATAL flaw... I'm refactoring.
#global COOLDOWN, DELAY_RANGE
#if(time.time() - LAST_TIME > COOLDOWN):
rand_delay = random.randint(0, DELAY_RANGE)
logger.debug("rand_delay: " + str(rand_delay) + " seconds")
logger.warning("Waiting " + str(rand_delay) + " seconds before buying...");
time.sleep(rand_delay)
#COOLDOWN = rand_delay
#logger.info("COOLDOWN: " + str(COOLDOWN) + " seconds")
# select the first item in the list (most profit) (sorted above)
#item_id = ITEMS[0]['item']['item_id']
#item_hash_name = ITEMS[0]['item']['item_hash_name']
#buy_usd = ITEMS[0]['item']['buy_prices']['usd']
# remove the item from the list
#ITEMS.pop(0)
# input(f"Buy some shit? Profit: ${profit} -- hit enter to continue...") # TODO: remove this
status = buy_item_by_id(item_id, buy_usd, item_hash_name)
wait(5) #TODO: stupid
if not status['success']:
logger.error(f"({item_hash_name}) - Cannot complete order. {status['message']}")
ITEMS_MISSED = ITEMS_MISSED + 1
embed = discord_webhook.DiscordEmbed(title=f"({item_hash_name}) - N/A", description=f"{status['message']}", color='ff0000')
else:
logger.info(f"({item_hash_name}) - item bought successfully.")
PROFIT = PROFIT + profit
ITEMS_BOUGHT = ITEMS_BOUGHT + 1
embed = discord_webhook.DiscordEmbed(title=f"({item_hash_name}) - ${profit:.2f} net",
description=f"{status['message']}", color='43cf3c')
# update GUI component (maybe)
global GUI_OBJECTS
GUI_OBJECTS['subtext_str'].set(f"Profit: ${PROFIT:.2f} | Items bought: {ITEMS_BOUGHT} | Items missed: {ITEMS_MISSED}")
GUI_OBJECTS['subtext_str'].config(textvariable=GUI_OBJECTS['subtext_str'])
logger.critical(f"Profit: ${PROFIT:.2f} | Items bought: {ITEMS_BOUGHT} | Items missed: {ITEMS_MISSED}")
# Discord notification ... above & below
embed.set_author(name="the ghost of gambling", url="https://rat.church",
icon_url="https://i.imgur.com/In7Urki.jpg")
embed.set_footer(text="END (or start?) GAMBLING NOW", icon_url="https://i.imgur.com/6Oqrqsi.jpg",
url="https://rat.church")
embed.set_timestamp()
embed.add_embed_field(name="Items bought", value=f"{ITEMS_BOUGHT}",inline=True)
embed.add_embed_field(name="Items missed", value=f"{ITEMS_MISSED}",inline=True)
embed.add_embed_field(name="PROFIT THIS SESSION", value=f"${PROFIT:.2f}")
embed.add_embed_field(name="Total items", value=f"{ITEMS_BOUGHT + ITEMS_MISSED}")
embed.add_embed_field(name="Profit per item", value=f"${PROFIT / (ITEMS_BOUGHT + ITEMS_MISSED):.2f}")
webhook.add_embed(embed=embed)
webhook.execute(remove_embeds=True)
# acknowledge message
ch.basic_ack(delivery_tag=method.delivery_tag)
#else:
#global ELAPSED_TIME, ELAPSED_STR
#ELAPSED_TIME = COOLDOWN - LAST_TIME
#if ELAPSED_STR is not None:
#ELAPSED_STR.set("cooldown left: " + str(COOLDOWN - ELAPSED_TIME))
# https://stackoverflow.com/a/63625977
def get_browser_log_entries(driver):
"""get log entreies from selenium and add to python logger before returning"""
loglevels = { 'NOTSET':0 , 'DEBUG':10 ,'INFO': 20 , 'WARNING':30, 'ERROR':40, 'SEVERE':40, 'CRITICAL':50}
#initialise a logger
browserlog = pylogger.getLogger("tf2ez")
#get browser logs
slurped_logs = driver.get_log('browser')
for entry in slurped_logs:
#convert broswer log to python log format
rec = browserlog.makeRecord("%s.%s"%(browserlog.name,entry['source']),loglevels.get(entry['level']),'.',0,entry['message'],None,None)
rec.created = entry['timestamp'] /1000 # log using original timestamp.. us -> ms
try:
#add browser log to python log
browserlog.handle(rec)
except:
print(entry)
#and return logs incase you want them
return slurped_logs
class BuyListener:
# constructor passthrough configuration items
channel = None
config = None
connection = None
login_method = None
scrape_url = None
delay_range = None
pika_host = None
pika_port = None
pika_username = None
pika_password = None
pika_queue = None
def __init__(self, pika_host, pika_port, pika_username, pika_password, pika_queue, delay_range, scrape_url, login_method):
self.delay_range = delay_range
self.connection = None
self.channel = None
self.scrape_url = scrape_url
self.pika_host = pika_host
self.pika_port = pika_port
self.pika_username = pika_username
self.pika_password = pika_password
self.pika_queue = pika_queue
self.login_method = login_method
# make sure URL ends with a slash
if self.scrape_url is not None:
if not scrape_url.endswith("/"):
self.scrape_url = scrape_url + "/"
# def delayed_passthrough(self, name, obj, callback=None):
# global GUI_OBJECTS
# GUI_OBJECTS[name] = obj
#
# if name == 'textHandler':
# self.text_handler = obj
#
# if name == 'subtext_str':
# GUI_OBJECTS['subtext_str'].set(f"Profit: ${PROFIT:.2f} | Items bought: {ITEMS_BOUGHT} | Items missed: {ITEMS_MISSED}")
# GUI_OBJECTS['subtext_str'].config(textvariable=GUI_OBJECTS['subtext_str'])
#
# if callback is not None:
# callback([name, obj])
# else:
# return obj
def init_selenium_and_login(self):
logger.warning("Initializing Selenium Webdriver, logging in...")
if os.getenv("login_method") is not None:
login_method = os.getenv("login_method")
else: # default to config.ini
login_method = self.login_method
# ---------------- Configure Driver Options ---------------- #
options = webdriver.ChromeOptions()
options.headless = False
options.add_argument("--ignore-certificate-errors")
options.add_argument("--start-maximized")
#options.add_argument("--disable-extensions")
# ---------------- Init Driver, then Login ---------------- #
caps = chromedriver.options.DesiredCapabilities.CHROME.copy()
caps['acceptInsecureCerts'] = True
#caps['goog:loggerPrefs'] = {'browser': 'ALL'}
if OS != "windows":
try:
driver = uc.Chrome(options=options, executable_path=os.path.abspath("./chromedriver"), desired_capabilities=caps)
except:
logger.error("Couldn't find the default Google Chrome binaries. Perhaps you haven't installed Google "
"Chrome?")
logger.error("if you are on Ubuntu/Debian: make sure you have wget, then use the following command "
"to install the latest version of Google Chrome:")
logger.critical("$ wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb "
"&& sudo dpkg -i google-chrome-stable_current_amd64.deb")
input("Press any key to exit...")
exit(1)
else:
driver = uc.Chrome(options=options, executable_path=os.path.abspath("./chromedriver"), desired_capabilities=caps)
#TODO: intrigue
#driver.add_virtual_authenticator()
global wait
wait = WebDriverWait(driver, 60)
driver.get(self.scrape_url)
login_method = login_method.lower()
logger.info("login method is defined as: " + login_method)
if login_method == "steam":
driver.find_element(By.CLASS_NAME, "login-block").click()
time.sleep(1)
wait.until(EC.title_contains("TF2EASY.COM"))
#TODO: FIX -- this does land you on steam login, but we need the AUTHENTICATED page which comes afterwards.
# landing on the steam OpenID login page, we need to click the login button -- but
# we'd like to grab the steam username for error reporting as well as like
# a steamid64 to link to them (too hard) and a base64 of their avatar for a nice little
# profile picture in the embed
STEAM_USERNAME = os.getenv("STEAM_USERNAME")
STEAM_AVATAR_TINYLINK = os.getenv("STEAM_AVATAR_TINYLINK")
if STEAM_USERNAME is None or STEAM_USERNAME == "":
STEAM_USERNAME = driver.find_element(By.CLASS_NAME, "OpenID_loggedInAccount").get_property("innerText")
logger.debug("STEAM_USERNAME = " + STEAM_USERNAME)
os.set_key('.env', 'STEAM_USERNAME', STEAM_USERNAME)
if STEAM_AVATAR_TINYLINK is None or STEAM_AVATAR_TINYLINK == "":
STEAM_AVATAR_TINYLINK = driver.find_element(By.CSS_SELECTOR, "#openidForm > div > div.OpenID_UserContainer > div.playerAvatar.online > img").get_attribute("src")
logger.debug("STEAM_AVATAR_TINYLINK = " + STEAM_AVATAR_TINYLINK)
os.set_key('.env', 'STEAM_AVATAR_TINYLINK', STEAM_AVATAR_TINYLINK)
elif login_method == "cookie":
login_cookie = os.getenv("login_cookie")
driver.add_cookie({
"name": "laravel_session",
"value": login_cookie
})
driver.refresh()
else:
logger.critical("You have to select login method in config.ini!") # not true anymore
wait(6)
logger.info("... wait... you don't need to include the login method in config.ini!!")
logger.info("Welp, try again")
return
try:
wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'menu-username-text')))
if login_method == "steam":
generated_cookie = driver.get_cookie("laravel_session")['value']
dotenv.set_key('.env', 'LOGIN_METHOD', 'cookie')
dotenv.set_key('.env', 'LOGIN_COOKIE', generated_cookie)
logger.info("Login method is now set to cookie and cookie is saved in .env file.")
except:
logger.critical("Couldn't login. (Wrong cookie?)")
dotenv.set_key('.env', 'LOGIN_METHOD', 'steam')
dotenv.set_key('.env', 'LOGIN_COOKIE', '')
logger.info("Login method is now set to steam and expired cookie is removed from .env file.")
return
logger.info("LOGGED IN SUCCESSFULLY!")
logger.warning("Click start to connect and begin consuming to the message queue. Click stop to disconnect.")
logger.info("Navigating to market section...")
driver.get(self.scrape_url + "market")
# wait for market nav to load
wait.until(EC.visibility_of_element_located((By.CLASS_NAME, 'new-item-info-holder')))
def start(self):
logger.add("rabbitmq", level="DEBUG", colorize=True, format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}")
# logger = logger.getLogger("rabbitmq").setLevel("DEBUG")
# logger.addHandler(text_handler)
# _EARLY_LOG_HANDLER = logger.StreamHandler(sys.stdout)
# log = logger.getLogger()
# log.addHandler(_EARLY_LOG_HANDLER)
# if level is not None:
# log.setLevel(level)
#
# _EARLY_ERR_HANDLER = logger.StreamHandler(sys.stderr)
# log = logger.getLogger()
# log.addHandler(_EARLY_LOG_HANDLER)
# if level is not None:
# log.setLevel(level)
#
# _EARLY_LOG_HANDLER.setLevel(logger.getLogger().level)
# _EARLY_ERR_HANDLER.setLevel(logger.getLogger().level)
# def get_selenium_log_entries(driver):
# consolemsgs = get_browser_log_entries(driver)
#
# def print_selenium_log_entires( driver ):
# logger.basicConfig(level=logger.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
# logger = logger.getLogger("selenium").setLevel("DEBUG")
# logger.addHandler(text_handler)
# get_selenium_log_entries(driver)
# ---------------- Start listening for messages ---------------- #
logger.info("Starting to listen for messages...")
try:
self.connection = pika.BlockingConnection(
pika.ConnectionParameters(host=self.pika_host, port=self.pika_port, credentials=pika.PlainCredentials(self.pika_username, self.pika_password)))
self.channel = self.connection.channel()
logger.info("Connected to RabbitMQ server: channel opened, declaring queue...")
self.channel.queue_declare(queue=self.pika_queue, durable=True, auto_delete=False, exclusive=False)
logger.info("Queue declared...")
# TODO: Seemingly required... but why?
self.channel.confirm_delivery()
logger.info("confirm_delivery set...")
# self.channel.basic_qos(prefetch_count==0)
# logger.info("basic_qos set...")
self.channel.basic_consume(queue=self.pika_queue, on_message_callback=callback, auto_ack=True, durable=True, exclusive=False)
logger.info("basic_consume set up... now we must start consuming.")
# clear old deals so we don't waste time
# self.channel.queue_purge(queue=self.pika_queue)
logger.warn("CONSUMING...")
self.channel.start_consuming()
except Exception as e:
logger.critical("Error while connecting to RabbitMQ server: " + str(e))
self.stop()
def stop(self):
logger.critical("STOP ISSUED -- Stopping deal listener...")
if self.connection is not None :
logger.error("Closing connection to RabbitMQ server...")
self.connection.close()
if self.channel is not None :
logger.error("Closing channel to RabbitMQ server...")
self.channel.stop_consuming()
self.channel.close()
logger.info("Deal listener stopped.")
exit(0)
if driver is not None:
driver.quit()