From 5e198e2e9d6d0d640b4647d4ea9cd86e32ad8ad6 Mon Sep 17 00:00:00 2001
From: mcinj <98779161+mcinj@users.noreply.github.com>
Date: Fri, 20 May 2022 16:56:36 -0400
Subject: [PATCH 01/18] readme update
---
README.md | 28 +++++++++++++++++++++-------
1 file changed, 21 insertions(+), 7 deletions(-)
diff --git a/README.md b/README.md
index 66151da..5db342e 100644
--- a/README.md
+++ b/README.md
@@ -4,15 +4,32 @@ The bot is specially designed for [SteamGifts.com](https://www.steamgifts.com/)
### Features
- Automatically enters giveaways.
- Undetectable.
-- Π‘onfigurable.
+- Π‘onfigurable
+ - Can look at your steam wishlist games or the main page for games to enter
+ - When evaluating a giveaway
+ - `max_time_left` - if the time left on giveaway is > max time left, then don't enter it
+ - `max_entries` - if the entries on a giveaway are > than max entries, then don't enter it
+ - `minimum_points` - minimum number of points **in your account** needed before considering any giveaway
+ - `minimum_game_points` - if steamgifts.com point cost (ex. 1P, 5P, etc) is below this, don't enter it
+ - `blacklist_keywords` - if the giveaway name contains any of these words, don't enter it. this list can be blank.
+ - Notifications
+ - A pushover notifications can be sent to you when a win is detected.
+ - Webserver - A simple, simple, simple webserver than can be enabled (disabled by default) to show the config and logs
+ - `web.host` - the IP to listen on (ex. localhost, 0.0.0.0, 192.168.1.1, etc)
+ - `web.port` - the port to listen on
+ - `web.app_root` - the folder to serve up which can be used for reverse proxying this behind nginx/apache/etc
+ - `web.ssl` - if the traffic will be encrypted (http or https) using a self-signed cert
+ - `web.basic_auth` - simple basic auth settings can be enabled
- Sleeps to restock the points.
- Can run 24/7.
## Instructions
- 1. Rename `config/config.ini.example` to `config/config.ini`.
- 2. Add your PHPSESSION cookie to it.
- 3. Modifying the other settings is optional.
+1. Sign in on SteamGifts.com by Steam.
+2. Find PHPSESSID cookie in your browser.
+3. Rename `config/config.ini.example` to `config/config.ini`.
+4. Add your PHPSESSION cookie to `cookie` in `config/config.ini`
+5. Modifying the other settings is optional as defaults are set.
### Run from sources
@@ -41,6 +58,3 @@ docker run --name steamgifts -e TZ=America/New_York -d -v /path/to/the/config/fo
```
-
-### Help
-Please leave your feedback and bugs in `Issues` page.
From 3005228416cb769603e5e6cae557f76870120234 Mon Sep 17 00:00:00 2001
From: mcinj <98779161+mcinj@users.noreply.github.com>
Date: Fri, 20 May 2022 17:37:43 -0400
Subject: [PATCH 02/18] more logging
---
src/bot/giveaway.py | 1 +
src/bot/log.py | 8 ++++----
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/bot/giveaway.py b/src/bot/giveaway.py
index ae23574..64a9dbf 100644
--- a/src/bot/giveaway.py
+++ b/src/bot/giveaway.py
@@ -26,6 +26,7 @@ class Giveaway:
self.time_created_string = None
self.time_created_in_minutes = None
+ logger.debug(f"Giveaway html: {soup_item}")
icons = soup_item.select('a.giveaway__icon')
self.steam_url = icons[0]['href']
self.steam_app_id = self.get_steam_app_id(self.steam_url)
diff --git a/src/bot/log.py b/src/bot/log.py
index 3228982..cbdce24 100644
--- a/src/bot/log.py
+++ b/src/bot/log.py
@@ -4,20 +4,20 @@ from logging.handlers import RotatingFileHandler
# log info level logs to stdout and debug to debug file
-log_format = "%(levelname)s %(asctime)s - %(message)s"
+debug_log_format = "%(levelname)s %(asctime)s - [%(filename)s:%(lineno)d] - %(message)s"
logging.basicConfig(
handlers=[RotatingFileHandler(f"{os.getenv('BOT_CONFIG_DIR', './config')}/debug.log", maxBytes=500000, backupCount=10)],
level=logging.DEBUG,
- format=log_format)
+ format=debug_log_format)
console_output = logging.StreamHandler()
console_output.setLevel(logging.INFO)
-console_format = logging.Formatter(log_format)
+console_format = logging.Formatter(debug_log_format)
console_output.setFormatter(console_format)
info_log_file = RotatingFileHandler(f"{os.getenv('BOT_CONFIG_DIR', './config')}/info.log", maxBytes=100000, backupCount=10)
info_log_file.setLevel(logging.INFO)
-info_log_format = logging.Formatter(log_format)
+info_log_format = logging.Formatter(debug_log_format)
info_log_file.setFormatter(info_log_format)
logging.root.addHandler(console_output)
From 6a5e679aecfc37ff17794ebbf816480da25b1e73 Mon Sep 17 00:00:00 2001
From: mcinj <98779161+mcinj@users.noreply.github.com>
Date: Fri, 20 May 2022 23:42:42 -0400
Subject: [PATCH 03/18] fix ref
---
src/bot/templates/configuration.html | 3 ++-
src/bot/templates/log.html | 3 ++-
src/bot/webserver_thread.py | 15 +++++++++++----
3 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/src/bot/templates/configuration.html b/src/bot/templates/configuration.html
index db2c119..7268e62 100644
--- a/src/bot/templates/configuration.html
+++ b/src/bot/templates/configuration.html
@@ -11,7 +11,8 @@
diff --git a/src/bot/templates/log.html b/src/bot/templates/log.html
index 80b7b71..9b486de 100644
--- a/src/bot/templates/log.html
+++ b/src/bot/templates/log.html
@@ -11,7 +11,8 @@
diff --git a/src/bot/webserver_thread.py b/src/bot/webserver_thread.py
index c62ebf0..6f6aeba 100644
--- a/src/bot/webserver_thread.py
+++ b/src/bot/webserver_thread.py
@@ -1,3 +1,4 @@
+import os
import threading
from threading import Thread
@@ -38,13 +39,19 @@ class WebServerThread(threading.Thread):
@app.route(f"{self.app_root}")
def config():
- with open('../config/config.ini', 'r') as f:
+ with open(f"{os.getenv('BOT_CONFIG_DIR', './config')}/config.ini", 'r') as f:
content = f.read()
return render_template('configuration.html', content=content)
- @app.route(f"{self.app_root}log")
- def logs():
- with open('../config/info.log', 'r') as f:
+ @app.route(f"{self.app_root}log_info")
+ def log_info():
+ with open(f"{os.getenv('BOT_CONFIG_DIR', './config')}/info.log", 'r') as f:
+ content = f.read()
+ return render_template('log.html', content=content)
+
+ @app.route(f"{self.app_root}log_debug")
+ def log_debug():
+ with open(f"{os.getenv('BOT_CONFIG_DIR', './config')}/debug.log", 'r') as f:
content = f.read()
return render_template('log.html', content=content)
From 9f2b2e7be2f066b4d6c0e1d899b51e524bfd57a6 Mon Sep 17 00:00:00 2001
From: mcinj <98779161+mcinj@users.noreply.github.com>
Date: Sat, 21 May 2022 13:12:04 -0400
Subject: [PATCH 04/18] removed unused column
---
.dockerignore | 1 -
.gitignore | 1 -
alembic.ini | 102 ++++++++++++++++++
...0_25_41-15c028536ef5_won_column_removed.py | 31 ++++++
src/bot/database.py | 2 -
src/bot/models.py | 1 -
6 files changed, 133 insertions(+), 5 deletions(-)
create mode 100644 alembic.ini
create mode 100644 src/alembic/versions/2022_05_21-10_25_41-15c028536ef5_won_column_removed.py
diff --git a/.dockerignore b/.dockerignore
index fb2bb68..c56db2a 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,5 +2,4 @@ config/config.ini
config/debug.log
config/sqlite.db
config/info.log
-alembic.ini
README.ini
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index e292dc2..6dc7536 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,5 @@
env/
venv/
-*.ini
__pycache__/
.idea
config/config.ini
diff --git a/alembic.ini b/alembic.ini
new file mode 100644
index 0000000..06ec989
--- /dev/null
+++ b/alembic.ini
@@ -0,0 +1,102 @@
+# A generic, single database configuration.
+
+[alembic]
+# path to migration scripts
+script_location = src/alembic
+
+# template used to generate migration files
+file_template = %%(year)d_%%(month).2d_%%(day).2d-%%(hour).2d_%%(minute).2d_%%(second).2d-%%(rev)s_%%(slug)s
+
+# sys.path path, will be prepended to sys.path if present.
+# defaults to the current working directory.
+prepend_sys_path = .
+
+# timezone to use when rendering the date within the migration file
+# as well as the filename.
+# If specified, requires the python-dateutil library that can be
+# installed by adding `alembic[tz]` to the pip requirements
+# string value is passed to dateutil.tz.gettz()
+# leave blank for localtime
+# timezone =
+
+# max length of characters to apply to the
+# "slug" field
+# truncate_slug_length = 40
+
+# set to 'true' to run the environment during
+# the 'revision' command, regardless of autogenerate
+# revision_environment = false
+
+# set to 'true' to allow .pyc and .pyo files without
+# a source .py file to be detected as revisions in the
+# versions/ directory
+# sourceless = false
+
+# version location specification; This defaults
+# to alembic/versions. When using multiple version
+# directories, initial revisions must be specified with --version-path.
+# The path separator used here should be the separator specified by "version_path_separator" below.
+# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions:src/alembic/versions
+
+# version path separator; As mentioned above, this is the character used to split
+# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
+# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
+# Valid values for version_path_separator are:
+#
+# version_path_separator = :
+# version_path_separator = ;
+# version_path_separator = space
+# version_path_separator = os # Use os.pathsep. Default configuration used for new projects.
+
+# the output encoding used when revision files
+# are written from script.py.mako
+# output_encoding = utf-8
+
+sqlalchemy.url = sqlite:///config/sqlite.db
+
+
+[post_write_hooks]
+# post_write_hooks defines scripts or Python functions that are run
+# on newly generated revision scripts. See the documentation for further
+# detail and examples
+
+# format using "black" - use the console_scripts runner, against the "black" entrypoint
+# hooks = black
+# black.type = console_scripts
+# black.entrypoint = black
+# black.options = -l 79 REVISION_SCRIPT_FILENAME
+
+# Logging configuration
+[loggers]
+keys = root,sqlalchemy,alembic
+
+[handlers]
+keys = console
+
+[formatters]
+keys = generic
+
+[logger_root]
+level = WARN
+handlers = console
+qualname =
+
+[logger_sqlalchemy]
+level = WARN
+handlers =
+qualname = sqlalchemy.engine
+
+[logger_alembic]
+level = INFO
+handlers =
+qualname = alembic
+
+[handler_console]
+class = StreamHandler
+args = (sys.stderr,)
+level = NOTSET
+formatter = generic
+
+[formatter_generic]
+format = %(levelname)-5.5s [%(name)s] %(message)s
+datefmt = %H:%M:%S
diff --git a/src/alembic/versions/2022_05_21-10_25_41-15c028536ef5_won_column_removed.py b/src/alembic/versions/2022_05_21-10_25_41-15c028536ef5_won_column_removed.py
new file mode 100644
index 0000000..7abd9af
--- /dev/null
+++ b/src/alembic/versions/2022_05_21-10_25_41-15c028536ef5_won_column_removed.py
@@ -0,0 +1,31 @@
+"""won column removed
+
+Revision ID: 15c028536ef5
+Revises: 1da33402b659
+Create Date: 2022-05-21 10:25:41.647723
+
+"""
+import sqlalchemy as sa
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision = '15c028536ef5'
+down_revision = '1da33402b659'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ op.drop_column('giveaway', 'won')
+
+
+def downgrade():
+ # add columns as nullable as existing records don't have that column value set
+ op.add_column('giveaway', sa.Column('won', sa.Boolean(), nullable=True))
+ # set value on new columns for all records
+ with op.get_context().autocommit_block():
+ op.execute("UPDATE giveaway SET won=false WHERE won is null;")
+ # SQLite doesn't support ALTERs so alembic uses a batch mode
+ # Set columns to non-nullable now that all records have a value
+ with op.batch_alter_table('giveaway') as batch_op:
+ batch_op.alter_column('won', nullable=False)
diff --git a/src/bot/database.py b/src/bot/database.py
index 849fcad..ec6beea 100644
--- a/src/bot/database.py
+++ b/src/bot/database.py
@@ -124,7 +124,6 @@ class GiveawayHelper:
copies=giveaway.copies,
contributor_level=giveaway.contributor_level,
entered=entered,
- won=False,
game_entries=giveaway.game_entries)
session.add(g)
session.commit()
@@ -147,7 +146,6 @@ class GiveawayHelper:
copies=giveaway.copies,
contributor_level=giveaway.contributor_level,
entered=entered,
- won=False,
game_entries=giveaway.game_entries)
session.merge(g)
session.commit()
\ No newline at end of file
diff --git a/src/bot/models.py b/src/bot/models.py
index f9174f8..c16b9f2 100644
--- a/src/bot/models.py
+++ b/src/bot/models.py
@@ -42,7 +42,6 @@ class TableGiveaway(Base):
copies = Column(Integer(), nullable=False)
contributor_level = Column(Integer(), nullable=False)
entered = Column(Boolean(), nullable=False)
- won = Column(Boolean(), nullable=False)
game_entries = Column(Integer(), nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
From 7c6493cea87586f3ac92c8479765f3dd89b248b2 Mon Sep 17 00:00:00 2001
From: mcinj <98779161+mcinj@users.noreply.github.com>
Date: Sun, 22 May 2022 14:20:44 -0400
Subject: [PATCH 05/18] re-org and db views
---
.gitignore | 2 +-
main.py | 2 +-
src/bot/database.py | 25 +++++---
src/{bot => }/run.py | 18 +++---
src/{bot => web}/static/css/main.css | 0
src/{bot => web}/templates/configuration.html | 4 +-
src/web/templates/giveaways.html | 63 +++++++++++++++++++
src/{bot => web}/templates/log.html | 4 +-
src/web/templates/notifications.html | 51 +++++++++++++++
src/{bot => web}/webserver_thread.py | 18 ++++--
10 files changed, 160 insertions(+), 27 deletions(-)
rename src/{bot => }/run.py (86%)
rename src/{bot => web}/static/css/main.css (100%)
rename src/{bot => web}/templates/configuration.html (74%)
create mode 100644 src/web/templates/giveaways.html
rename src/{bot => web}/templates/log.html (74%)
create mode 100644 src/web/templates/notifications.html
rename src/{bot => web}/webserver_thread.py (76%)
diff --git a/.gitignore b/.gitignore
index 6dc7536..8304bd8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,5 +4,5 @@ __pycache__/
.idea
config/config.ini
config/*.log*
-config/sqlite.db
+config/sqlite*
.DS_STORE
\ No newline at end of file
diff --git a/main.py b/main.py
index 3131670..d19fa56 100644
--- a/main.py
+++ b/main.py
@@ -1,4 +1,4 @@
-from src.bot import run
+from src import run
if __name__ == '__main__':
diff --git a/src/bot/database.py b/src/bot/database.py
index ec6beea..e3fc60e 100644
--- a/src/bot/database.py
+++ b/src/bot/database.py
@@ -6,7 +6,7 @@ from alembic import command
from alembic.config import Config
from dateutil import tz
from sqlalchemy import func
-from sqlalchemy.orm import Session
+from sqlalchemy.orm import Session, joinedload
from sqlalchemy_utils import database_exists
from .log import get_logger
@@ -17,11 +17,7 @@ engine = sqlalchemy.create_engine(f"{os.getenv('BOT_DB_URL', 'sqlite:///./config
engine.connect()
-def create_engine(db_url: str):
- return engine
-
-
-def run_migrations(script_location: str, db_url: str) -> None:
+def run_db_migrations(script_location: str, db_url: str) -> None:
logger.debug('Running DB migrations in %r on %r', script_location, db_url)
alembic_cfg = Config()
alembic_cfg.set_main_option('script_location', script_location)
@@ -30,10 +26,9 @@ def run_migrations(script_location: str, db_url: str) -> None:
if not database_exists(db_url):
logger.debug(f"'{db_url}' does not exist. Running normal migration to create db and tables." )
command.upgrade(alembic_cfg, 'head')
- if database_exists(db_url):
- logger.debug(f"'{db_url} exists.")
- e = create_engine(db_url)
- insp = sqlalchemy.inspect(e)
+ elif database_exists(db_url):
+ logger.debug(f"'{db_url}' exists.")
+ insp = sqlalchemy.inspect(engine)
alembic_version_table_name = 'alembic_version'
has_alembic_table = insp.has_table(alembic_version_table_name)
if has_alembic_table:
@@ -49,6 +44,11 @@ def run_migrations(script_location: str, db_url: str) -> None:
class NotificationHelper:
+ @classmethod
+ def get(cls):
+ with Session(engine) as session:
+ return session.query(TableNotification).order_by(TableNotification.created_at.desc()).all()
+
@classmethod
def insert(cls, type_of_error, message, medium, success):
with Session(engine) as session:
@@ -89,6 +89,11 @@ class NotificationHelper:
class GiveawayHelper:
+ @classmethod
+ def get(cls):
+ with Session(engine) as session:
+ return session.query(TableGiveaway).options(joinedload('steam_item')).order_by(TableGiveaway.giveaway_ended_at.desc()).all()
+
@classmethod
def unix_timestamp_to_utc_datetime(cls, timestamp):
return datetime.utcfromtimestamp(timestamp)
diff --git a/src/bot/run.py b/src/run.py
similarity index 86%
rename from src/bot/run.py
rename to src/run.py
index c5993fb..2375878 100644
--- a/src/bot/run.py
+++ b/src/run.py
@@ -1,13 +1,13 @@
import os
from time import sleep
-from .log import get_logger
-from .config_reader import ConfigReader, ConfigException
-from .enter_giveaways import SteamGiftsException
-from .giveaway_thread import GiveawayThread
-from .notification import Notification
-from .database import run_migrations, create_engine
-from .webserver_thread import WebServerThread
+from src.bot.log import get_logger
+from src.bot.config_reader import ConfigReader, ConfigException
+from src.bot.enter_giveaways import SteamGiftsException
+from src.bot.giveaway_thread import GiveawayThread
+from src.bot.notification import Notification
+from src.bot.database import run_db_migrations
+from src.web.webserver_thread import WebServerThread
logger = get_logger(__name__)
config_file_name = f"{os.getenv('BOT_CONFIG_DIR', './config')}/config.ini"
@@ -73,9 +73,9 @@ def entry():
|___/
-------------------------------------------------------------------------------------
""")
- run_migrations(alembic_migration_files, db_url)
- create_engine(db_url)
+ run_db_migrations(alembic_migration_files, db_url)
run()
+
if __name__ == '__main__':
entry()
diff --git a/src/bot/static/css/main.css b/src/web/static/css/main.css
similarity index 100%
rename from src/bot/static/css/main.css
rename to src/web/static/css/main.css
diff --git a/src/bot/templates/configuration.html b/src/web/templates/configuration.html
similarity index 74%
rename from src/bot/templates/configuration.html
rename to src/web/templates/configuration.html
index 7268e62..670abb9 100644
--- a/src/bot/templates/configuration.html
+++ b/src/web/templates/configuration.html
@@ -1,7 +1,7 @@