From 1cbe40c8c887ee8b66d3958a4ad610a30d030d27 Mon Sep 17 00:00:00 2001 From: mcinj <98779161+mcinj@users.noreply.github.com> Date: Sat, 23 Apr 2022 15:00:22 -0400 Subject: [PATCH] removed cli and added filtering --- .dockerignore | 1 + .gitignore | 3 +- Dockerfile | 2 + README.md | 11 +--- src/cli.py | 127 ----------------------------------------- src/config.ini.example | 11 ++++ src/log.py | 13 +++++ src/main.py | 76 ++++++++++++++++-------- src/run.py | 72 +++++++++++++++++++++-- 9 files changed, 149 insertions(+), 167 deletions(-) create mode 100644 .dockerignore delete mode 100644 src/cli.py create mode 100644 src/config.ini.example create mode 100644 src/log.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d088fa3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +src/config.ini \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5cbc0ff..8e5348c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ env/ *.ini __pycache__/ -.idea \ No newline at end of file +.idea +config \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 360298a..34af0f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,4 +14,6 @@ COPY requirements.txt . RUN pip3 install -r requirements.txt COPY ./src/* /app/ +VOLUME /config + CMD ["python", "run.py"] diff --git a/README.md b/README.md index e904bcd..ebb1d6c 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,13 @@ -![](https://i.imgur.com/oCob3wQ.gif) - ### About The bot is specially designed for [SteamGifts.com](https://www.steamgifts.com/) ### Features - Automatically enters giveaways. - Undetectable. -- Сonvenient user interface. - Сonfigurable. - Sleeps to restock the points. - Can run 24/7. -### How to run -1. Download the latest version: https://github.com/stilManiac/steamgifts-bot/releases -2. Sign in on [SteamGifts.com](https://www.steamgifts.com/) by Steam. -3. Find `PHPSESSID` cookie in your browser. -4. Start the bot and follow instructions. - ### Run from sources ```bash python -m venv env @@ -27,7 +18,7 @@ python src/cli.py ### Docker -#### Run it using a hub.docker.com image +#### Run it ```bash # Run the container docker run --name steamgifts -d -it mcinj/docker-steamgifts-bot:latest diff --git a/src/cli.py b/src/cli.py deleted file mode 100644 index bbd7f3d..0000000 --- a/src/cli.py +++ /dev/null @@ -1,127 +0,0 @@ -import six -import configparser -import re - -from pyfiglet import figlet_format -from pyconfigstore import ConfigStore -from PyInquirer import (Token, ValidationError, Validator, print_json, prompt, - style_from_dict) -from prompt_toolkit import document - -try: - import colorama - colorama.init() -except ImportError: - colorama = None - -try: - from termcolor import colored -except ImportError: - colored = None - -config = configparser.ConfigParser() - -style = style_from_dict({ - Token.QuestionMark: '#fac731 bold', - Token.Answer: '#4688f1 bold', - Token.Selected: '#0abf5b', # default - Token.Pointer: '#673ab7 bold', -}) - -def log(string, color, font="slant", figlet=False): - if colored: - if not figlet: - six.print_(colored(string, color)) - else: - six.print_(colored(figlet_format( - string, font=font), color)) - else: - six.print_(string) - - -class PointValidator(Validator): - def validate(self, document: document.Document): - value = document.text - try: - value = int(value) - except Exception: - raise ValidationError(message = 'Value should be greater than 0', cursor_position = len(document.text)) - - if value <= 0: - raise ValidationError(message = 'Value should be greater than 0', cursor_position = len(document.text)) - return True - - -def ask(type, name, message, validate=None, choices=[]): - questions = [ - { - 'type': type, - 'name': name, - 'message': message, - 'validate': validate, - }, - ] - if choices: - questions[0].update({ - 'choices': choices, - }) - answers = prompt(questions, style=style) - return answers - - -def run(): - from main import SteamGifts as SG - - def askCookie(): - cookie = ask(type='input', - name='cookie', - message='Enter PHPSESSID cookie (Only needed to provide once):') - config['DEFAULT']['cookie'] = cookie['cookie'] - - with open('config.ini', 'w') as configfile: - config.write(configfile) - return cookie['cookie'] - - log("SteamGifts Bot", color="blue", figlet=True) - log("Welcome to SteamGifts Bot!", "green") - log("Created by: github.com/stilManiac", "white") - - config.read('config.ini') - if not config['DEFAULT'].get('cookie'): - cookie = askCookie() - else: - re_enter_cookie = ask(type='confirm', - name='reenter', - message='Do you want to enter new cookie?')['reenter'] - if re_enter_cookie: - cookie = askCookie() - else: - cookie = config['DEFAULT'].get('cookie') - - pinned_games = ask(type='confirm', - name='pinned', - message='Should bot enter pinned games?')['pinned'] - - gift_type = ask(type='list', - name='gift_type', - message='Select type:', - choices=[ - 'All', - 'Wishlist', - 'Recommended', - 'Copies', - 'DLC', - 'New' - ])['gift_type'] - - min_points = ask(type='input', - name='min_points', - message='Enter minimum points to start working (bot will try to enter giveaways until minimum value is reached):', - validate=PointValidator)['min_points'] - - s = SG(cookie, gift_type, pinned_games, min_points) - s.start() - - -if __name__ == '__main__': - run() \ No newline at end of file diff --git a/src/config.ini.example b/src/config.ini.example new file mode 100644 index 0000000..4fa98e8 --- /dev/null +++ b/src/config.ini.example @@ -0,0 +1,11 @@ +[DEFAULT] +cookie = PHPSESSIONCOOKIEGOESHERE +# gift_types options: All, Wishlist, Recommended, Copies, DLC, New +gift_types = All +pinned = true +# minimum number of points in your account before entering into giveaways +minimum_points = 50 +# max number of entries in a giveaway for it to be considered +max_entries = 900 +# time left in minutes of a giveaway for it to be considered +max_time_left = 180 diff --git a/src/log.py b/src/log.py new file mode 100644 index 0000000..8fa3584 --- /dev/null +++ b/src/log.py @@ -0,0 +1,13 @@ +import logging +import sys + +Log_Format = "%(levelname)s %(asctime)s - %(message)s" + +logging.basicConfig(stream = sys.stdout, + filemode = "w", + format = Log_Format, + level = logging.INFO) + + +def get_logger(name): + return logging.getLogger(name) diff --git a/src/main.py b/src/main.py index b65813c..f8551fc 100644 --- a/src/main.py +++ b/src/main.py @@ -1,27 +1,27 @@ -import sys -import configparser -import requests import json -import threading +import re +from random import randint +from time import sleep +import requests +from bs4 import BeautifulSoup from requests.adapters import HTTPAdapter from urllib3.util import Retry -from time import sleep -from random import randint -from requests import RequestException -from bs4 import BeautifulSoup -from cli import log +import log +logger = log.get_logger(__name__) class SteamGifts: - def __init__(self, cookie, gifts_type, pinned, min_points): + def __init__(self, cookie, gifts_type, pinned, min_points, max_entries, max_time_left): self.cookie = { 'PHPSESSID': cookie } self.gifts_type = gifts_type self.pinned = pinned self.min_points = int(min_points) + self.max_entries = int(max_entries) + self.max_time_left = int(max_time_left) self.base = "https://www.steamgifts.com" self.session = requests.Session() @@ -66,15 +66,36 @@ class SteamGifts: self.xsrf_token = soup.find('input', {'name': 'xsrf_token'})['value'] self.points = int(soup.find('span', {'class': 'nav__points'}).text) # storage points except TypeError: - log("⛔ Cookie is not valid.", "red") + logger.error("⛔ Cookie is not valid.") sleep(10) exit() + def determine_time_in_minutes(self, string_time): + if not string_time: + logger.error(f"Could not determine time from string {string_time}") + return None + match = re.search('(?P[0-9]+) (?P(hour|day|minute|second))', string_time) + if match: + number = int(match.group('number')) + time_unit = match.group('time_unit') + if time_unit == 'hour': + return number * 60 + elif time_unit == 'day': + return number * 24 * 60 + elif time_unit == 'minute': + return number + elif time_unit == 'second': + return 1 + else: + return None + else: + return None + def get_game_content(self, page=1): n = page while True: txt = "⚙️ Retrieving games from %d page." % n - log(txt, "magenta") + logger.info(txt) filtered_url = self.filter_url[self.gifts_type] % n paginated_url = f"{self.base}/giveaways/{filtered_url}" @@ -86,7 +107,7 @@ class SteamGifts: if not len(game_list): random_seconds = randint(900, 1400) txt = f"We have run out of gifts to consider. Trying again in {random_seconds} seconds." - log(txt, "yellow") + logger.info(txt) sleep(random_seconds) self.start() continue @@ -98,7 +119,7 @@ class SteamGifts: if self.points == 0 or self.points < self.min_points: random_seconds = randint(900, 1400) txt = f"🛋️ Sleeping {random_seconds} seconds to get more points. We have {self.points} points, but we need {self.min_points} to start." - log(txt, "yellow") + logger.info(txt) sleep(random_seconds) self.start() break @@ -112,29 +133,38 @@ class SteamGifts: continue times = item.select('div span[data-timestamp]') game_remaining = times[0].text + game_remaining_in_minutes = self.determine_time_in_minutes(game_remaining) game_created = times[1].text - game_entries = item.select('div.giveaway__links span')[0].text + game_created_in_minutes = self.determine_time_in_minutes(game_created) + game_entries = int(item.select('div.giveaway__links span')[0].text.split(' ')[0].replace(',' ,'')) txt = f"{game_name} {game_cost} - {game_entries} - Created {game_created} ago with {game_remaining} remaining." - log(txt, 'grey') + logger.debug(txt) if self.points - int(game_cost) < 0: txt = f"⛔ Not enough points to enter: {game_name}" - log(txt, "red") + logger.info(txt) continue - - elif self.points - int(game_cost) >= 0: + if self.max_time_left > game_remaining_in_minutes: + txt = f"Game {game_name} has {game_remaining_in_minutes} left and is above your cutoff of {self.max_time_left} minutes." + logger.info(txt) + continue + if self.max_entries > game_entries: + txt = f"Game {game_name} has {game_entries} entries is above your cutoff of {self.max_entries} entries." + logger.info(txt) + continue + # defensive move + if self.points - int(game_cost) >= 0: res = self.entry_gift(game_id) if res: self.points -= int(game_cost) txt = f"🎉 One more game! Has just entered {game_name}" - log(txt, "green") + logger.info(txt) sleep(randint(3, 7)) n = n+1 - - log("🛋️ List of games is ended. Waiting 2 mins to update...", "yellow") + logger.info("🛋️ List of games is ended. Waiting 2 mins to update...") sleep(120) self.start() @@ -151,6 +181,6 @@ class SteamGifts: if self.points > 0: txt = "🤖 Hoho! I am back! You have %d points. Lets hack." % self.points - log(txt, "blue") + logger.info(txt) self.get_game_content() diff --git a/src/run.py b/src/run.py index e0a72a4..011c2a5 100644 --- a/src/run.py +++ b/src/run.py @@ -1,21 +1,81 @@ import configparser +from configparser import ConfigParser +import log + +logger = log.get_logger(__name__) config = configparser.ConfigParser() +class MyException(Exception): + pass + + +def value_range(min, max): + return [str(x) for x in [*range(min, max + 1)]] + + +class MyConfig(ConfigParser): + def __init__(self, config_file): + super(MyConfig, self).__init__() + + self.read(config_file) + self.validate_config() + + def validate_config(self): + required_values = { + 'DEFAULT': { + 'gift_types': ('All', 'Wishlist', 'Recommended', 'Copies', 'DLC', 'New'), + 'pinned': ('true', 'false'), + 'minimum_points': '%s' % (value_range(0,400)), + 'max_entries': '%s' % (value_range(0,10000)), + 'max_time_left': '%s' % (value_range(0,21600)) + } + } + + for section, keys in required_values.items(): + if section not in self: + raise MyException( + 'Missing section %s in the config file' % section) + + for key, values in keys.items(): + if key not in self[section] or self[section][key] == '': + raise MyException(( + 'Missing value for %s under section %s in ' + + 'the config file') % (key, section)) + + if values: + if self[section][key] not in values: + raise MyException(( + 'Invalid value for %s under section %s in ' + + 'the config file') % (key, section)) + + def run(): from main import SteamGifts as SG - config.read('config.ini') + file_name = '../config/config.ini' + try: + with open(file_name) as f: + config.read_file(f) + MyConfig(file_name) + except IOError: + txt = f"{file_name} doesn't exist. Rename {file_name}.example to {file_name} and fill out." + logger.warning(txt) + exit(-1) + except MyException as e: + logger.error(e) + exit(-1) + + config.read(file_name) cookie = config['DEFAULT'].get('cookie') - pinned_games = config['DEFAULT'].getboolean('pinned') - gift_types = config['DEFAULT'].get('gift_types') + minimum_points = config['DEFAULT'].getint('minimum_points') + max_entries = config['DEFAULT'].getint('max_entries') + max_time_left = config['DEFAULT'].getint('max_time_left') - min_points = config['DEFAULT'].getint('minimum_points') - - s = SG(cookie, gift_types, pinned_games, min_points) + s = SG(cookie, gift_types, pinned_games, minimum_points, max_entries, max_time_left) s.start()