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 01/15] 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 02/15] 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 @@ - Steamgifts Bot Configuration + {{name}} Steamgifts Bot Configuration @@ -13,6 +13,8 @@
  • Config
  • Info Logs
  • Debug Logs
  • +
  • Giveaways
  • +
  • Notifications
  • diff --git a/src/web/templates/giveaways.html b/src/web/templates/giveaways.html new file mode 100644 index 0000000..e35376d --- /dev/null +++ b/src/web/templates/giveaways.html @@ -0,0 +1,63 @@ + + + + {{name}} Steamgifts Bot DB View + + + +
    +
    +

    Steamgifts Bot

    + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + {% for row in content %} + + + + + + + + + + + + + + + {% endfor %} + +
    Giveaway IDSteam IDGiveaway URINameSteam URLUserGiveaway Created AtGiveaway Ended AtCostCopiesContributor LevelEntered
    {{row.giveaway_id}}{{row.steam_id}}{{row.giveaway_uri}}{{row.steam_item.game_name}}{{row.steam_item.steam_url}}{{row.user}}{{row.giveaway_created_at}}{{row.giveaway_ended_at}}{{row.cost}}{{row.copies}}{{row.contributor_level}}{{row.entered}}
    +
    + + \ No newline at end of file diff --git a/src/bot/templates/log.html b/src/web/templates/log.html similarity index 74% rename from src/bot/templates/log.html rename to src/web/templates/log.html index 9b486de..8a7408f 100644 --- a/src/bot/templates/log.html +++ b/src/web/templates/log.html @@ -1,7 +1,7 @@ - Steamgifts Bot Logs + {{name}} Steamgifts Bot {{log_type}} Logs @@ -13,6 +13,8 @@
  • Config
  • Info Logs
  • Debug Logs
  • +
  • Giveaways
  • +
  • Notifications
  • diff --git a/src/web/templates/notifications.html b/src/web/templates/notifications.html new file mode 100644 index 0000000..b11fe88 --- /dev/null +++ b/src/web/templates/notifications.html @@ -0,0 +1,51 @@ + + + + {{name}} Steamgifts Bot DB View + + + +
    +
    +

    Steamgifts Bot

    + + + +
    +
    +
    + + + + + + + + + + + + + {% for row in content %} + + + + + + + + + {% endfor %} + +
    IDMediumTypeSuccessCreated AtMessage
    {{row.id}}{{row.medium}}{{row.type}}{{row.success}}{{row.created_at}}{{row.message}}
    +
    + + \ No newline at end of file diff --git a/src/bot/webserver_thread.py b/src/web/webserver_thread.py similarity index 76% rename from src/bot/webserver_thread.py rename to src/web/webserver_thread.py index 6f6aeba..4456801 100644 --- a/src/bot/webserver_thread.py +++ b/src/web/webserver_thread.py @@ -4,7 +4,8 @@ from threading import Thread from flask_basicauth import BasicAuth -from .log import get_logger +from src.bot.database import NotificationHelper, GiveawayHelper +from src.bot.log import get_logger logger = get_logger(__name__) @@ -15,6 +16,7 @@ class WebServerThread(threading.Thread): Thread.__init__(self) self.exc = None self.config = config + self.name = config['NOTIFICATIONS'].get('notification.prefix') self.host = config['WEB'].get('web.host') self.port = config['WEB'].getint('web.port') self.ssl = config['WEB'].getboolean('web.ssl') @@ -41,24 +43,32 @@ class WebServerThread(threading.Thread): def config(): with open(f"{os.getenv('BOT_CONFIG_DIR', './config')}/config.ini", 'r') as f: content = f.read() - return render_template('configuration.html', content=content) + return render_template('configuration.html', name=self.name, content=content) @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) + return render_template('log.html', name=self.name, log_type='info', 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) + return render_template('log.html', name=self.name, log_type='debug', content=content) @app.route(f"{self.app_root}alive") def alive(): return 'OK' + @app.route(f"{self.app_root}notifications") + def db_notifications(): + return render_template('notifications.html', content=NotificationHelper.get()) + + @app.route(f"{self.app_root}giveaways") + def db_giveaways(): + return render_template('giveaways.html', content=GiveawayHelper.get()) + if self.enabled: logger.info("Webserver Enabled. Running") if self.ssl: From e7a4e90988da6ab63a7334cccd109a63faa5dbee Mon Sep 17 00:00:00 2001 From: mcinj <98779161+mcinj@users.noreply.github.com> Date: Tue, 24 May 2022 08:27:54 -0400 Subject: [PATCH 03/15] prettify (but still ugly) the giveaway table --- requirements.txt | 5 +++-- src/bot/database.py | 12 +++++++++++- src/web/static/css/main.css | 30 ++++++++++++++++++++++++++++++ src/web/templates/giveaways.html | 16 ++++++++++++++-- src/web/webserver_thread.py | 17 +++++++++-------- 5 files changed, 67 insertions(+), 13 deletions(-) diff --git a/requirements.txt b/requirements.txt index 47e10c5..517508f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,8 +3,9 @@ beautifulsoup4==4.11.1 urllib3==1.26.9 sqlalchemy==1.4.36 sqlalchemy_utils==0.38.2 +paginate-sqlalchemy==0.3.1 +alembic==1.7.7 python-dateutil==2.8.2 Flask==2.1.2 Flask-BasicAuth==0.2.0 -pyopenssl==22.0.0 -alembic==1.7.7 \ No newline at end of file +pyopenssl==22.0.0 \ No newline at end of file diff --git a/src/bot/database.py b/src/bot/database.py index e3fc60e..913986a 100644 --- a/src/bot/database.py +++ b/src/bot/database.py @@ -1,6 +1,7 @@ import os from datetime import datetime, timedelta +import paginate_sqlalchemy import sqlalchemy from alembic import command from alembic.config import Config @@ -92,7 +93,16 @@ 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() + return session.query(TableGiveaway).options(joinedload('steam_item'))\ + .order_by(TableGiveaway.giveaway_ended_at.desc()).all() + + @classmethod + def paginate(cls, page=1): + with Session(engine) as session: + paginated_giveaways = paginate_sqlalchemy.SqlalchemyOrmPage(session.query(TableGiveaway) + .options(joinedload('steam_item')) + .order_by(TableGiveaway.giveaway_ended_at.desc()), page=page) + return paginated_giveaways @classmethod def unix_timestamp_to_utc_datetime(cls, timestamp): diff --git a/src/web/static/css/main.css b/src/web/static/css/main.css index 9613db1..8936f21 100644 --- a/src/web/static/css/main.css +++ b/src/web/static/css/main.css @@ -73,4 +73,34 @@ h3 { .menu li a { color: #444; text-decoration: none; +} +.styled-table { + border-collapse: collapse; + margin: 25px 0; + font-size: 0.9em; + font-family: sans-serif; + min-width: 400px; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.15); +} +.styled-table thead tr { + background-color: #009879; + color: #ffffff; + text-align: left; +} +.styled-table th, +.styled-table td { + padding: 12px 15px; +} +.styled-table tbody tr { + border-bottom: 1px solid #dddddd; +} +.styled-table tbody tr:nth-of-type(even) { + background-color: #f3f3f3; +} +.styled-table tbody tr:last-of-type { + border-bottom: 2px solid #009879; +} +.styled-table tbody tr.active-row { + font-weight: bold; + color: #009879; } \ No newline at end of file diff --git a/src/web/templates/giveaways.html b/src/web/templates/giveaways.html index e35376d..d33163c 100644 --- a/src/web/templates/giveaways.html +++ b/src/web/templates/giveaways.html @@ -22,7 +22,19 @@
    - + +
    @@ -40,7 +52,7 @@ - {% for row in content %} + {% for row in content.items %} diff --git a/src/web/webserver_thread.py b/src/web/webserver_thread.py index 4456801..9cf74b8 100644 --- a/src/web/webserver_thread.py +++ b/src/web/webserver_thread.py @@ -16,7 +16,7 @@ class WebServerThread(threading.Thread): Thread.__init__(self) self.exc = None self.config = config - self.name = config['NOTIFICATIONS'].get('notification.prefix') + self.prefix = config['NOTIFICATIONS'].get('notification.prefix') self.host = config['WEB'].get('web.host') self.port = config['WEB'].getint('web.port') self.ssl = config['WEB'].getboolean('web.ssl') @@ -43,19 +43,19 @@ class WebServerThread(threading.Thread): def config(): with open(f"{os.getenv('BOT_CONFIG_DIR', './config')}/config.ini", 'r') as f: content = f.read() - return render_template('configuration.html', name=self.name, content=content) + return render_template('configuration.html', name=self.prefix, content=content) @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', name=self.name, log_type='info', content=content) + return render_template('log.html', name=self.prefix, log_type='info', 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', name=self.name, log_type='debug', content=content) + return render_template('log.html', name=self.prefix, log_type='debug', content=content) @app.route(f"{self.app_root}alive") def alive(): @@ -63,11 +63,12 @@ class WebServerThread(threading.Thread): @app.route(f"{self.app_root}notifications") def db_notifications(): - return render_template('notifications.html', content=NotificationHelper.get()) + return render_template('notifications.html', name=self.prefix, content=NotificationHelper.get()) - @app.route(f"{self.app_root}giveaways") - def db_giveaways(): - return render_template('giveaways.html', content=GiveawayHelper.get()) + @app.route(f"{self.app_root}giveaways", methods=['GET'], defaults={"page": 1}) + @app.route(f"{self.app_root}giveaways/", methods=['GET']) + def db_giveaways(page): + return render_template('giveaways.html', name=self.prefix, content=GiveawayHelper.paginate(page=page)) if self.enabled: logger.info("Webserver Enabled. Running") From 7a8d2fe79302aebccabffe4ad8389ca450e9d8a6 Mon Sep 17 00:00:00 2001 From: mcinj <98779161+mcinj@users.noreply.github.com> Date: Tue, 24 May 2022 09:15:25 -0400 Subject: [PATCH 04/15] stateful notifications --- ...31_25-ff0a728ba3da_add_games_won_column.py | 27 +++++++++++++++++++ src/bot/database.py | 7 ++--- src/bot/enter_giveaways.py | 16 ++++++++--- src/bot/models.py | 1 + src/bot/notification.py | 16 +++++------ 5 files changed, 52 insertions(+), 15 deletions(-) create mode 100644 src/alembic/versions/2022_05_24-08_31_25-ff0a728ba3da_add_games_won_column.py diff --git a/src/alembic/versions/2022_05_24-08_31_25-ff0a728ba3da_add_games_won_column.py b/src/alembic/versions/2022_05_24-08_31_25-ff0a728ba3da_add_games_won_column.py new file mode 100644 index 0000000..e6c0c79 --- /dev/null +++ b/src/alembic/versions/2022_05_24-08_31_25-ff0a728ba3da_add_games_won_column.py @@ -0,0 +1,27 @@ +"""add games_won column + +Revision ID: ff0a728ba3da +Revises: 15c028536ef5 +Create Date: 2022-05-24 08:31:25.684099 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'ff0a728ba3da' +down_revision = '15c028536ef5' +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column('notification', sa.Column('games_won', sa.Integer(), nullable=True)) + # set a default for previous won notifications + with op.get_context().autocommit_block(): + op.execute("UPDATE notification SET games_won=1 WHERE type='won';") + + +def downgrade(): + op.drop_column('notification', 'games_won') diff --git a/src/bot/database.py b/src/bot/database.py index 913986a..ff42668 100644 --- a/src/bot/database.py +++ b/src/bot/database.py @@ -51,9 +51,10 @@ class NotificationHelper: return session.query(TableNotification).order_by(TableNotification.created_at.desc()).all() @classmethod - def insert(cls, type_of_error, message, medium, success): + def insert(cls, type_of_error, message, medium, success, number_won): with Session(engine) as session: - n = TableNotification(type=type_of_error, message=message, medium=medium, success=success) + n = TableNotification(type=type_of_error, message=message, medium=medium, success=success, + games_won=number_won) session.add(n) session.commit() @@ -65,7 +66,7 @@ class NotificationHelper: within_3_days = session.query(TableNotification) \ .filter(func.DATE(TableNotification.created_at) >= (datetime.utcnow().date() - timedelta(days=1))) \ .filter(func.DATE(TableNotification.created_at) <= (datetime.utcnow().date() + timedelta(days=1))) \ - .filter_by(type='won').all() + .filter_by(type='won').order_by(TableNotification.created_at.asc()).all() actual = [] for r in within_3_days: if r.created_at.replace(tzinfo=tz.tzutc()).astimezone(tz.tzlocal()).date() == datetime.now( diff --git a/src/bot/enter_giveaways.py b/src/bot/enter_giveaways.py index bcd25c8..9455c9b 100644 --- a/src/bot/enter_giveaways.py +++ b/src/bot/enter_giveaways.py @@ -89,14 +89,22 @@ class EnterGiveaways: won = soup.select("a[title='Giveaways Won'] div") if won: - number_won = soup.select_one("a[title='Giveaways Won'] div").text + number_won = int(soup.select_one("a[title='Giveaways Won'] div").text) won_notifications = NotificationHelper.get_won_notifications_today() if won_notifications and len(won_notifications) >= 1: - logger.info("🆒️ Win(s) detected, but we have already notified that there are won games waiting " - "to be received. Doing nothing.") + if number_won == won_notifications[-1].games_won: + logger.info("🆒️ Win(s) detected, but we have already notified that there are won games waiting " + "to be received. Doing nothing.") + elif number_won > won_notifications[-1].games_won: + logger.info("🔥🔥 MORE win(s) detected. Notifying again.") + self.notification.send_won(f"You won ANOTHER game. You now have {number_won} game(s) " + f"waiting to be claimed.", number_won) + else: # we have less games waiting to be claimed than notified, meaning some have been claimed + logger.info("🆒️ Win(s) detected, but we have already notified that there are won games waiting " + "to be received. Some have been claimed. Doing nothing.") else: logger.info(f"💰💰 WINNER! You have {number_won} game(s) waiting to be claimed. 💰💰") - self.notification.send_won(f"WINNER! You have {number_won} game(s) waiting to be claimed.") + self.notification.send_won(f"WINNER! You have {number_won} game(s) waiting to be claimed.", number_won) else: logger.debug('No wins detected. Doing nothing.') diff --git a/src/bot/models.py b/src/bot/models.py index c16b9f2..23272be 100644 --- a/src/bot/models.py +++ b/src/bot/models.py @@ -13,6 +13,7 @@ class TableNotification(Base): message = Column(String(300), nullable=False) medium = Column(String(50), nullable=False) success = Column(Boolean, nullable=False) + games_won = Column(Integer, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) diff --git a/src/bot/notification.py b/src/bot/notification.py index 7a3089b..14ef359 100644 --- a/src/bot/notification.py +++ b/src/bot/notification.py @@ -15,11 +15,11 @@ class Notification: self.pushover_user_key = None self.message_prefix = f"{message_prefix}: " - def send_won(self, message): - self.__send('won', message) + def send_won(self, message, number_won): + self.__send('won', message, number_won) def send_error(self, message): - self.__send('error', message) + self.__send('error', message, number_won=None) def enable_pushover(self, token, user_key): logger.debug("Enabling pushover notifications.") @@ -27,13 +27,13 @@ class Notification: self.pushover_token = token self.pushover_user_key = user_key - def __send(self, type_of_error, message): - logger.debug(f"Attempting to notify: {message}") + def __send(self, type_of_error, message, number_won=None): + logger.debug(f"Attempting to notify: '{message}'. Won: {number_won}") if self.pushover: logger.debug("Pushover enabled. Sending message.") - self.__pushover(type_of_error, message) + self.__pushover(type_of_error, message, number_won) - def __pushover(self, type_of_error, message): + def __pushover(self, type_of_error, message, number_won=None): conn = http.client.HTTPSConnection("api.pushover.net:443") conn.request("POST", "/1/messages.json", urllib.parse.urlencode({ @@ -48,4 +48,4 @@ class Notification: else: logger.error(f"Pushover notification failed. Code {response.getcode()}: {response.read().decode()}") success = False - NotificationHelper.insert(type_of_error, f"{message}", 'pushover', success) + NotificationHelper.insert(type_of_error, f"{message}", 'pushover', success, number_won) From f3e6c165f4e248976396be1d3b22f02efba3e8f3 Mon Sep 17 00:00:00 2001 From: mcinj <98779161+mcinj@users.noreply.github.com> Date: Thu, 26 May 2022 17:03:58 -0400 Subject: [PATCH 05/15] clean up html template --- src/web/templates/base.html | 26 ++++++++++++++++ src/web/templates/configuration.html | 32 ++++---------------- src/web/templates/giveaways.html | 44 +++++++--------------------- src/web/templates/log.html | 32 ++++---------------- src/web/templates/notifications.html | 34 ++++----------------- src/web/webserver_thread.py | 16 +++++----- 6 files changed, 63 insertions(+), 121 deletions(-) create mode 100644 src/web/templates/base.html diff --git a/src/web/templates/base.html b/src/web/templates/base.html new file mode 100644 index 0000000..0992170 --- /dev/null +++ b/src/web/templates/base.html @@ -0,0 +1,26 @@ + + + + {% block title %} {% endblock %} + + + +
    +
    +

    Steamgifts Bot

    + +
    +
    +
    + {% block content %} {% endblock %} +
    + + \ No newline at end of file diff --git a/src/web/templates/configuration.html b/src/web/templates/configuration.html index 670abb9..0e9979f 100644 --- a/src/web/templates/configuration.html +++ b/src/web/templates/configuration.html @@ -1,26 +1,6 @@ - - - - {{name}} Steamgifts Bot Configuration - - - -
    -
    -

    Steamgifts Bot

    - -
    -
    -
    -
    {{content}}
    -
    - - \ No newline at end of file +{% extends 'base.html' %} + +{% block title %} {{name}} Steamgifts Bot Configuration {% endblock %} +{% block content %} +
    {{data}}
    +{% endblock %} \ No newline at end of file diff --git a/src/web/templates/giveaways.html b/src/web/templates/giveaways.html index d33163c..b3e5f64 100644 --- a/src/web/templates/giveaways.html +++ b/src/web/templates/giveaways.html @@ -1,35 +1,15 @@ - - - - {{name}} Steamgifts Bot DB View - - - -
    -
    -

    Steamgifts Bot

    - - - -
    -
    -
    -
      - {% if content.previous_page %} -
    • Previous
    • +{% extends 'base.html' %} + +{% block title %} {{name}} Steamgifts Bot Giveaways {% endblock %} +{% block content %} +
        + {% if data.previous_page %} +
      • Previous
      • {% else %}
      • Previous
      • {% endif %} - {% if content.next_page %} -
      • Next
      • + {% if data.next_page %} +
      • Next
      • {% else %}
      • Next
      • {% endif %} @@ -52,7 +32,7 @@
    - {% for row in content.items %} + {% for row in data.items %} @@ -70,6 +50,4 @@ {% endfor %}
    Giveaway ID
    {{row.giveaway_id}} {{row.steam_id}}
    {{row.giveaway_id}} {{row.steam_id}}
    -
    - - \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/src/web/templates/log.html b/src/web/templates/log.html index 8a7408f..70960cf 100644 --- a/src/web/templates/log.html +++ b/src/web/templates/log.html @@ -1,26 +1,6 @@ - - - - {{name}} Steamgifts Bot {{log_type}} Logs - - - -
    -
    -

    Steamgifts Bot

    - -
    -
    -
    -
    {{content}}
    -
    - - \ No newline at end of file +{% extends 'base.html' %} + +{% block title %} {{name}} Steamgifts Bot {{log_type}} Logs{% endblock %} +{% block content %} +
    {{data}}
    +{% endblock %} \ No newline at end of file diff --git a/src/web/templates/notifications.html b/src/web/templates/notifications.html index b11fe88..6a9b518 100644 --- a/src/web/templates/notifications.html +++ b/src/web/templates/notifications.html @@ -1,28 +1,8 @@ - - - - {{name}} Steamgifts Bot DB View - - - -
    -
    -

    Steamgifts Bot

    - - - -
    -
    -
    - +{% extends 'base.html' %} + +{% block title %} {{name}} Steamgifts Bot Notifications {% endblock %} +{% block content %} +
    @@ -46,6 +26,4 @@ {% endfor %}
    ID
    -
    - - \ No newline at end of file +{% endblock %} \ No newline at end of file diff --git a/src/web/webserver_thread.py b/src/web/webserver_thread.py index 9cf74b8..f517722 100644 --- a/src/web/webserver_thread.py +++ b/src/web/webserver_thread.py @@ -42,20 +42,20 @@ class WebServerThread(threading.Thread): @app.route(f"{self.app_root}") def config(): with open(f"{os.getenv('BOT_CONFIG_DIR', './config')}/config.ini", 'r') as f: - content = f.read() - return render_template('configuration.html', name=self.prefix, content=content) + data = f.read() + return render_template('configuration.html', name=self.prefix, data=data) @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', name=self.prefix, log_type='info', content=content) + data = f.read() + return render_template('log.html', name=self.prefix, log_type='info', data=data) @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', name=self.prefix, log_type='debug', content=content) + data = f.read() + return render_template('log.html', name=self.prefix, log_type='debug', data=data) @app.route(f"{self.app_root}alive") def alive(): @@ -63,12 +63,12 @@ class WebServerThread(threading.Thread): @app.route(f"{self.app_root}notifications") def db_notifications(): - return render_template('notifications.html', name=self.prefix, content=NotificationHelper.get()) + return render_template('notifications.html', name=self.prefix, data=NotificationHelper.get()) @app.route(f"{self.app_root}giveaways", methods=['GET'], defaults={"page": 1}) @app.route(f"{self.app_root}giveaways/", methods=['GET']) def db_giveaways(page): - return render_template('giveaways.html', name=self.prefix, content=GiveawayHelper.paginate(page=page)) + return render_template('giveaways.html', name=self.prefix, data=GiveawayHelper.paginate(page=page)) if self.enabled: logger.info("Webserver Enabled. Running") From 8010bbd750fc8c7f9a326a1715d921bf494189f1 Mon Sep 17 00:00:00 2001 From: mcinj <98779161+mcinj@users.noreply.github.com> Date: Wed, 1 Jun 2022 07:58:06 -0400 Subject: [PATCH 06/15] stats --- src/bot/database.py | 21 ++++++++++++++++----- src/web/templates/base.html | 1 + src/web/templates/stats.html | 9 +++++++++ src/web/webserver_thread.py | 6 ++++++ 4 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 src/web/templates/stats.html diff --git a/src/bot/database.py b/src/bot/database.py index ff42668..65ebbe0 100644 --- a/src/bot/database.py +++ b/src/bot/database.py @@ -6,7 +6,7 @@ import sqlalchemy from alembic import command from alembic.config import Config from dateutil import tz -from sqlalchemy import func +from sqlalchemy import func, select from sqlalchemy.orm import Session, joinedload from sqlalchemy_utils import database_exists @@ -25,7 +25,7 @@ def run_db_migrations(script_location: str, db_url: str) -> None: alembic_cfg.set_main_option('sqlalchemy.url', db_url) if not database_exists(db_url): - logger.debug(f"'{db_url}' does not exist. Running normal migration to create db and tables." ) + logger.debug(f"'{db_url}' does not exist. Running normal migration to create db and tables.") command.upgrade(alembic_cfg, 'head') elif database_exists(db_url): logger.debug(f"'{db_url}' exists.") @@ -94,7 +94,7 @@ class GiveawayHelper: @classmethod def get(cls): with Session(engine) as session: - return session.query(TableGiveaway).options(joinedload('steam_item'))\ + return session.query(TableGiveaway).options(joinedload('steam_item')) \ .order_by(TableGiveaway.giveaway_ended_at.desc()).all() @classmethod @@ -102,9 +102,20 @@ class GiveawayHelper: with Session(engine) as session: paginated_giveaways = paginate_sqlalchemy.SqlalchemyOrmPage(session.query(TableGiveaway) .options(joinedload('steam_item')) - .order_by(TableGiveaway.giveaway_ended_at.desc()), page=page) + .order_by( + TableGiveaway.giveaway_ended_at.desc()), page=page) return paginated_giveaways + @classmethod + def total_giveaways(cls): + with Session(engine) as session: + return session.execute(select(func.count(TableGiveaway.giveaway_id))).scalar_one() + + @classmethod + def total_entered(cls): + with Session(engine) as session: + return session.query(TableGiveaway).filter_by(entered=True).count() + @classmethod def unix_timestamp_to_utc_datetime(cls, timestamp): return datetime.utcfromtimestamp(timestamp) @@ -164,4 +175,4 @@ class GiveawayHelper: entered=entered, game_entries=giveaway.game_entries) session.merge(g) - session.commit() \ No newline at end of file + session.commit() diff --git a/src/web/templates/base.html b/src/web/templates/base.html index 0992170..6922545 100644 --- a/src/web/templates/base.html +++ b/src/web/templates/base.html @@ -11,6 +11,7 @@