removed cli and added filtering
This commit is contained in:
parent
9e481fc2e3
commit
1cbe40c8c8
9 changed files with 149 additions and 167 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
src/config.ini
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@ env/
|
||||||
*.ini
|
*.ini
|
||||||
__pycache__/
|
__pycache__/
|
||||||
.idea
|
.idea
|
||||||
|
config
|
|
@ -14,4 +14,6 @@ COPY requirements.txt .
|
||||||
RUN pip3 install -r requirements.txt
|
RUN pip3 install -r requirements.txt
|
||||||
|
|
||||||
COPY ./src/* /app/
|
COPY ./src/* /app/
|
||||||
|
VOLUME /config
|
||||||
|
|
||||||
CMD ["python", "run.py"]
|
CMD ["python", "run.py"]
|
||||||
|
|
11
README.md
11
README.md
|
@ -1,22 +1,13 @@
|
||||||
![](https://i.imgur.com/oCob3wQ.gif)
|
|
||||||
|
|
||||||
### About
|
### About
|
||||||
The bot is specially designed for [SteamGifts.com](https://www.steamgifts.com/)
|
The bot is specially designed for [SteamGifts.com](https://www.steamgifts.com/)
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
- Automatically enters giveaways.
|
- Automatically enters giveaways.
|
||||||
- Undetectable.
|
- Undetectable.
|
||||||
- Сonvenient user interface.
|
|
||||||
- Сonfigurable.
|
- Сonfigurable.
|
||||||
- Sleeps to restock the points.
|
- Sleeps to restock the points.
|
||||||
- Can run 24/7.
|
- 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
|
### Run from sources
|
||||||
```bash
|
```bash
|
||||||
python -m venv env
|
python -m venv env
|
||||||
|
@ -27,7 +18,7 @@ python src/cli.py
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
#### Run it using a hub.docker.com image
|
#### Run it
|
||||||
```bash
|
```bash
|
||||||
# Run the container
|
# Run the container
|
||||||
docker run --name steamgifts -d -it mcinj/docker-steamgifts-bot:latest
|
docker run --name steamgifts -d -it mcinj/docker-steamgifts-bot:latest
|
||||||
|
|
127
src/cli.py
127
src/cli.py
|
@ -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()
|
|
11
src/config.ini.example
Normal file
11
src/config.ini.example
Normal file
|
@ -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
|
13
src/log.py
Normal file
13
src/log.py
Normal file
|
@ -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)
|
76
src/main.py
76
src/main.py
|
@ -1,27 +1,27 @@
|
||||||
import sys
|
|
||||||
import configparser
|
|
||||||
import requests
|
|
||||||
import json
|
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 requests.adapters import HTTPAdapter
|
||||||
from urllib3.util import Retry
|
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:
|
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 = {
|
self.cookie = {
|
||||||
'PHPSESSID': cookie
|
'PHPSESSID': cookie
|
||||||
}
|
}
|
||||||
self.gifts_type = gifts_type
|
self.gifts_type = gifts_type
|
||||||
self.pinned = pinned
|
self.pinned = pinned
|
||||||
self.min_points = int(min_points)
|
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.base = "https://www.steamgifts.com"
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
|
@ -66,15 +66,36 @@ class SteamGifts:
|
||||||
self.xsrf_token = soup.find('input', {'name': 'xsrf_token'})['value']
|
self.xsrf_token = soup.find('input', {'name': 'xsrf_token'})['value']
|
||||||
self.points = int(soup.find('span', {'class': 'nav__points'}).text) # storage points
|
self.points = int(soup.find('span', {'class': 'nav__points'}).text) # storage points
|
||||||
except TypeError:
|
except TypeError:
|
||||||
log("⛔ Cookie is not valid.", "red")
|
logger.error("⛔ Cookie is not valid.")
|
||||||
sleep(10)
|
sleep(10)
|
||||||
exit()
|
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<number>[0-9]+) (?P<time_unit>(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):
|
def get_game_content(self, page=1):
|
||||||
n = page
|
n = page
|
||||||
while True:
|
while True:
|
||||||
txt = "⚙️ Retrieving games from %d page." % n
|
txt = "⚙️ Retrieving games from %d page." % n
|
||||||
log(txt, "magenta")
|
logger.info(txt)
|
||||||
|
|
||||||
filtered_url = self.filter_url[self.gifts_type] % n
|
filtered_url = self.filter_url[self.gifts_type] % n
|
||||||
paginated_url = f"{self.base}/giveaways/{filtered_url}"
|
paginated_url = f"{self.base}/giveaways/{filtered_url}"
|
||||||
|
@ -86,7 +107,7 @@ class SteamGifts:
|
||||||
if not len(game_list):
|
if not len(game_list):
|
||||||
random_seconds = randint(900, 1400)
|
random_seconds = randint(900, 1400)
|
||||||
txt = f"We have run out of gifts to consider. Trying again in {random_seconds} seconds."
|
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)
|
sleep(random_seconds)
|
||||||
self.start()
|
self.start()
|
||||||
continue
|
continue
|
||||||
|
@ -98,7 +119,7 @@ class SteamGifts:
|
||||||
if self.points == 0 or self.points < self.min_points:
|
if self.points == 0 or self.points < self.min_points:
|
||||||
random_seconds = randint(900, 1400)
|
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."
|
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)
|
sleep(random_seconds)
|
||||||
self.start()
|
self.start()
|
||||||
break
|
break
|
||||||
|
@ -112,29 +133,38 @@ class SteamGifts:
|
||||||
continue
|
continue
|
||||||
times = item.select('div span[data-timestamp]')
|
times = item.select('div span[data-timestamp]')
|
||||||
game_remaining = times[0].text
|
game_remaining = times[0].text
|
||||||
|
game_remaining_in_minutes = self.determine_time_in_minutes(game_remaining)
|
||||||
game_created = times[1].text
|
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."
|
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:
|
if self.points - int(game_cost) < 0:
|
||||||
txt = f"⛔ Not enough points to enter: {game_name}"
|
txt = f"⛔ Not enough points to enter: {game_name}"
|
||||||
log(txt, "red")
|
logger.info(txt)
|
||||||
continue
|
continue
|
||||||
|
if self.max_time_left > game_remaining_in_minutes:
|
||||||
elif self.points - int(game_cost) >= 0:
|
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)
|
res = self.entry_gift(game_id)
|
||||||
if res:
|
if res:
|
||||||
self.points -= int(game_cost)
|
self.points -= int(game_cost)
|
||||||
txt = f"🎉 One more game! Has just entered {game_name}"
|
txt = f"🎉 One more game! Has just entered {game_name}"
|
||||||
log(txt, "green")
|
logger.info(txt)
|
||||||
sleep(randint(3, 7))
|
sleep(randint(3, 7))
|
||||||
|
|
||||||
n = n+1
|
n = n+1
|
||||||
|
|
||||||
|
logger.info("🛋️ List of games is ended. Waiting 2 mins to update...")
|
||||||
log("🛋️ List of games is ended. Waiting 2 mins to update...", "yellow")
|
|
||||||
sleep(120)
|
sleep(120)
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
|
@ -151,6 +181,6 @@ class SteamGifts:
|
||||||
|
|
||||||
if self.points > 0:
|
if self.points > 0:
|
||||||
txt = "🤖 Hoho! I am back! You have %d points. Lets hack." % self.points
|
txt = "🤖 Hoho! I am back! You have %d points. Lets hack." % self.points
|
||||||
log(txt, "blue")
|
logger.info(txt)
|
||||||
|
|
||||||
self.get_game_content()
|
self.get_game_content()
|
||||||
|
|
72
src/run.py
72
src/run.py
|
@ -1,21 +1,81 @@
|
||||||
import configparser
|
import configparser
|
||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
import log
|
||||||
|
|
||||||
|
logger = log.get_logger(__name__)
|
||||||
config = configparser.ConfigParser()
|
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():
|
def run():
|
||||||
from main import SteamGifts as SG
|
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')
|
cookie = config['DEFAULT'].get('cookie')
|
||||||
|
|
||||||
pinned_games = config['DEFAULT'].getboolean('pinned')
|
pinned_games = config['DEFAULT'].getboolean('pinned')
|
||||||
|
|
||||||
gift_types = config['DEFAULT'].get('gift_types')
|
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, minimum_points, max_entries, max_time_left)
|
||||||
|
|
||||||
s = SG(cookie, gift_types, pinned_games, min_points)
|
|
||||||
s.start()
|
s.start()
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue