Compare commits
10 Commits
7c6493cea8
...
cc1cbc08f4
Author | SHA1 | Date |
---|---|---|
mcinj | cc1cbc08f4 | |
mcinj | 66a812ddd6 | |
mcinj | 8dc2721efe | |
mcinj | 113e111e9d | |
mcinj | 48c030f445 | |
mcinj | de2379587e | |
mcinj | 8010bbd750 | |
mcinj | f3e6c165f4 | |
mcinj | 7a8d2fe793 | |
mcinj | e7a4e90988 |
|
@ -3,8 +3,10 @@ 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
|
||||
apscheduler==3.9.1
|
|
@ -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')
|
|
@ -0,0 +1,31 @@
|
|||
"""won column added backed
|
||||
|
||||
Revision ID: 8c1784114d65
|
||||
Revises: ff0a728ba3da
|
||||
Create Date: 2022-06-01 09:20:36.762279
|
||||
|
||||
"""
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8c1784114d65'
|
||||
down_revision = 'ff0a728ba3da'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# 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)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('giveaway', 'won')
|
|
@ -1,11 +1,12 @@
|
|||
import os
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import paginate_sqlalchemy
|
||||
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
|
||||
|
||||
|
@ -24,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.")
|
||||
|
@ -50,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()
|
||||
|
||||
|
@ -64,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(
|
||||
|
@ -92,7 +94,37 @@ 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 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 total_won(cls):
|
||||
with Session(engine) as session:
|
||||
return session.query(TableGiveaway).filter_by(won=True).count()
|
||||
|
||||
@classmethod
|
||||
def get_by_giveaway_id(cls, game_id):
|
||||
with Session(engine) as session:
|
||||
return session.query(TableGiveaway).filter_by(giveaway_id=game_id).one_or_none()
|
||||
|
||||
@classmethod
|
||||
def unix_timestamp_to_utc_datetime(cls, timestamp):
|
||||
|
@ -105,7 +137,18 @@ class GiveawayHelper:
|
|||
steam_id=giveaway.steam_app_id).all()
|
||||
|
||||
@classmethod
|
||||
def insert(cls, giveaway, entered):
|
||||
def mark_game_as_won(cls, game_id):
|
||||
with Session(engine) as session:
|
||||
result = session.query(TableGiveaway).filter_by(giveaway_id=game_id).all()
|
||||
if result:
|
||||
won_giveaway = result[0]
|
||||
if not won_giveaway.won:
|
||||
won_giveaway.won = True
|
||||
session.add(won_giveaway)
|
||||
session.commit()
|
||||
|
||||
@classmethod
|
||||
def insert(cls, giveaway, entered, won):
|
||||
with Session(engine) as session:
|
||||
result = session.query(TableSteamItem).filter_by(steam_id=giveaway.steam_app_id).all()
|
||||
if result:
|
||||
|
@ -129,15 +172,16 @@ class GiveawayHelper:
|
|||
copies=giveaway.copies,
|
||||
contributor_level=giveaway.contributor_level,
|
||||
entered=entered,
|
||||
won=won,
|
||||
game_entries=giveaway.game_entries)
|
||||
session.add(g)
|
||||
session.commit()
|
||||
|
||||
@classmethod
|
||||
def upsert_giveaway(cls, giveaway, entered):
|
||||
def upsert_giveaway_with_details(cls, giveaway, entered, won):
|
||||
result = GiveawayHelper.get_by_ids(giveaway)
|
||||
if not result:
|
||||
GiveawayHelper.insert(giveaway, entered)
|
||||
GiveawayHelper.insert(giveaway, entered, won)
|
||||
else:
|
||||
with Session(engine) as session:
|
||||
g = TableGiveaway(
|
||||
|
@ -151,6 +195,28 @@ class GiveawayHelper:
|
|||
copies=giveaway.copies,
|
||||
contributor_level=giveaway.contributor_level,
|
||||
entered=entered,
|
||||
won=won,
|
||||
game_entries=giveaway.game_entries)
|
||||
session.merge(g)
|
||||
session.commit()
|
||||
session.commit()
|
||||
|
||||
@classmethod
|
||||
def upsert_giveaway(cls, giveaway):
|
||||
result = GiveawayHelper.get_by_ids(giveaway)
|
||||
if not result:
|
||||
GiveawayHelper.insert(giveaway, False, False)
|
||||
else:
|
||||
with Session(engine) as session:
|
||||
g = TableGiveaway(
|
||||
giveaway_id=giveaway.giveaway_game_id,
|
||||
steam_id=result[0].steam_id,
|
||||
giveaway_uri=giveaway.giveaway_uri,
|
||||
user=giveaway.user,
|
||||
giveaway_created_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_created_timestamp),
|
||||
giveaway_ended_at=GiveawayHelper.unix_timestamp_to_utc_datetime(giveaway.time_remaining_timestamp),
|
||||
cost=giveaway.cost,
|
||||
copies=giveaway.copies,
|
||||
contributor_level=giveaway.contributor_level,
|
||||
game_entries=giveaway.game_entries)
|
||||
session.merge(g)
|
||||
session.commit()
|
||||
|
|
|
@ -9,7 +9,7 @@ from urllib3.util import Retry
|
|||
|
||||
from .log import get_logger
|
||||
from .database import NotificationHelper, GiveawayHelper
|
||||
from .giveaway import Giveaway
|
||||
from .giveaway_entry import GiveawayEntry
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
@ -19,28 +19,29 @@ class SteamGiftsException(Exception):
|
|||
|
||||
|
||||
class EnterGiveaways:
|
||||
|
||||
def __init__(self, cookie, user_agent, gifts_type, pinned, min_points, max_entries,
|
||||
max_time_left, minimum_game_points, blacklist, notification):
|
||||
self.contributor_level = None
|
||||
self.xsrf_token = None
|
||||
self.points = None
|
||||
self.cookie = {
|
||||
self._contributor_level = None
|
||||
self._xsrf_token = None
|
||||
self._points = None
|
||||
self._cookie = {
|
||||
'PHPSESSID': cookie
|
||||
}
|
||||
self.user_agent = user_agent
|
||||
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.minimum_game_points = int(minimum_game_points)
|
||||
self.blacklist = blacklist.split(',')
|
||||
self.notification = notification
|
||||
self._user_agent = user_agent
|
||||
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._minimum_game_points = int(minimum_game_points)
|
||||
self._blacklist = blacklist.split(',')
|
||||
self._notification = notification
|
||||
|
||||
self.base = "https://www.steamgifts.com"
|
||||
self.session = requests.Session()
|
||||
self._base = "https://www.steamgifts.com"
|
||||
self._session = requests.Session()
|
||||
|
||||
self.filter_url = {
|
||||
self._filter_url = {
|
||||
'All': "search?page=%d",
|
||||
'Wishlist': "search?page=%d&type=wishlist",
|
||||
'Recommended': "search?page=%d&type=recommended",
|
||||
|
@ -49,12 +50,23 @@ class EnterGiveaways:
|
|||
'New': "search?page=%d&type=new"
|
||||
}
|
||||
|
||||
def requests_retry_session(
|
||||
def start(self):
|
||||
self._update_info()
|
||||
if self._points >= self._min_points:
|
||||
txt = f"〰 You have {self._points} points. Evaluating '{self._gifts_type}' giveaways..."
|
||||
logger.info(txt)
|
||||
self._evaluate_giveaways()
|
||||
else:
|
||||
txt = f"🟡 You have {self._points} points which is below your minimum point threshold of " \
|
||||
f"{self._min_points} points for '{self._gifts_type}' giveaways. Not evaluating right now."
|
||||
logger.info(txt)
|
||||
|
||||
def _requests_retry_session(
|
||||
self,
|
||||
retries=5,
|
||||
backoff_factor=0.3
|
||||
):
|
||||
session = self.session or requests.Session()
|
||||
session = self._session or requests.Session()
|
||||
retry = Retry(
|
||||
total=retries,
|
||||
read=retries,
|
||||
|
@ -67,85 +79,93 @@ class EnterGiveaways:
|
|||
session.mount('https://', adapter)
|
||||
return session
|
||||
|
||||
def get_soup_from_page(self, url):
|
||||
def _get_soup_from_page(self, url):
|
||||
headers = {
|
||||
'User-Agent': self.user_agent
|
||||
'User-Agent': self._user_agent
|
||||
}
|
||||
self.requests_retry_session().get(url, headers=headers)
|
||||
r = requests.get(url, cookies=self.cookie)
|
||||
self._requests_retry_session().get(url, headers=headers)
|
||||
r = requests.get(url, cookies=self._cookie)
|
||||
soup = BeautifulSoup(r.text, 'html.parser')
|
||||
return soup
|
||||
|
||||
def update_info(self):
|
||||
soup = self.get_soup_from_page(self.base)
|
||||
def _update_info(self):
|
||||
soup = self._get_soup_from_page(self._base)
|
||||
|
||||
try:
|
||||
self.xsrf_token = soup.find('input', {'name': 'xsrf_token'})['value']
|
||||
self.points = int(soup.find('span', {'class': 'nav__points'}).text) # storage points
|
||||
self.contributor_level = int(float(soup.select_one('nav a>span[title]')['title']))
|
||||
self._xsrf_token = soup.find('input', {'name': 'xsrf_token'})['value']
|
||||
self._points = int(soup.find('span', {'class': 'nav__points'}).text) # storage points
|
||||
self._contributor_level = int(float(soup.select_one('nav a>span[title]')['title']))
|
||||
except TypeError:
|
||||
logger.error("⛔⛔⛔ Cookie is not valid. A new one must be added.⛔⛔⛔")
|
||||
raise SteamGiftsException("Cookie is not valid. A new one must be added.")
|
||||
|
||||
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.')
|
||||
|
||||
def should_we_enter_giveaway(self, giveaway):
|
||||
def _should_we_enter_giveaway(self, giveaway):
|
||||
if giveaway.time_remaining_in_minutes is None:
|
||||
return False
|
||||
if giveaway.time_created_in_minutes is None:
|
||||
return False
|
||||
|
||||
if self.blacklist is not None and self.blacklist != ['']:
|
||||
for keyword in self.blacklist:
|
||||
if self._blacklist is not None and self._blacklist != ['']:
|
||||
for keyword in self._blacklist:
|
||||
if giveaway.game_name.lower().find(keyword.lower()) != -1:
|
||||
txt = f"〰️ Game {giveaway.game_name} contains the blacklisted keyword {keyword}"
|
||||
logger.info(txt)
|
||||
return False
|
||||
if giveaway.contributor_level is None or self.contributor_level < giveaway.contributor_level:
|
||||
if giveaway.contributor_level is None or self._contributor_level < giveaway.contributor_level:
|
||||
txt = f"〰️ Game {giveaway.game_name} requires at least level {giveaway.contributor_level} contributor " \
|
||||
f"level to enter. Your level: {self.contributor_level}"
|
||||
f"level to enter. Your level: {self._contributor_level}"
|
||||
logger.info(txt)
|
||||
return False
|
||||
if self.points - int(giveaway.cost) < 0:
|
||||
if giveaway.time_remaining_in_minutes > self._max_time_left:
|
||||
txt = f"〰️ Game {giveaway.game_name} has {giveaway.time_remaining_in_minutes} minutes left and is " \
|
||||
f"above your cutoff of {self._max_time_left} minutes."
|
||||
logger.info(txt)
|
||||
return False
|
||||
if giveaway.game_entries / giveaway.copies > self._max_entries:
|
||||
txt = f"〰️ Game {giveaway.game_name} has {giveaway.game_entries} entries and is above your cutoff " \
|
||||
f"of {self._max_entries} entries."
|
||||
logger.info(txt)
|
||||
return False
|
||||
if self._points - int(giveaway.cost) < 0:
|
||||
txt = f"〰️ Not enough points to enter: {giveaway.game_name}"
|
||||
logger.info(txt)
|
||||
return False
|
||||
if giveaway.cost < self.minimum_game_points:
|
||||
if giveaway.cost < self._minimum_game_points:
|
||||
txt = f"〰️ Game {giveaway.game_name} costs {giveaway.cost}P and is below your cutoff of " \
|
||||
f"{self.minimum_game_points}P."
|
||||
logger.info(txt)
|
||||
return False
|
||||
if giveaway.time_remaining_in_minutes > self.max_time_left:
|
||||
txt = f"〰️ Game {giveaway.game_name} has {giveaway.time_remaining_in_minutes} minutes left and is " \
|
||||
f"above your cutoff of {self.max_time_left} minutes."
|
||||
logger.info(txt)
|
||||
return False
|
||||
if giveaway.game_entries / giveaway.copies > self.max_entries:
|
||||
txt = f"〰️ Game {giveaway.game_name} has {giveaway.game_entries} entries and is above your cutoff " \
|
||||
f"of {self.max_entries} entries."
|
||||
f"{self._minimum_game_points}P."
|
||||
logger.info(txt)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def enter_giveaway(self, giveaway):
|
||||
def _enter_giveaway(self, giveaway):
|
||||
headers = {
|
||||
'User-Agent': self.user_agent
|
||||
'User-Agent': self._user_agent
|
||||
}
|
||||
payload = {'xsrf_token': self.xsrf_token, 'do': 'entry_insert', 'code': giveaway.giveaway_game_id}
|
||||
payload = {'xsrf_token': self._xsrf_token, 'do': 'entry_insert', 'code': giveaway.giveaway_game_id}
|
||||
logger.debug(f"Sending enter giveaway payload: {payload}")
|
||||
entry = requests.post('https://www.steamgifts.com/ajax.php', data=payload, cookies=self.cookie,
|
||||
entry = requests.post('https://www.steamgifts.com/ajax.php', data=payload, cookies=self._cookie,
|
||||
headers=headers)
|
||||
json_data = json.loads(entry.text)
|
||||
|
||||
|
@ -156,17 +176,17 @@ class EnterGiveaways:
|
|||
logger.error(f"❌ Failed entering giveaway {giveaway.giveaway_game_id}: {json_data}")
|
||||
return False
|
||||
|
||||
def evaluate_giveaways(self, page=1):
|
||||
def _evaluate_giveaways(self, page=1):
|
||||
n = page
|
||||
run = True
|
||||
while run and n < 3: # hard stop safety net at page 3 as idk why we would ever get to this point
|
||||
txt = "〰️ Retrieving games from %d page." % n
|
||||
logger.info(txt)
|
||||
|
||||
filtered_url = self.filter_url[self.gifts_type] % n
|
||||
paginated_url = f"{self.base}/giveaways/{filtered_url}"
|
||||
filtered_url = self._filter_url[self._gifts_type] % n
|
||||
paginated_url = f"{self._base}/giveaways/{filtered_url}"
|
||||
|
||||
soup = self.get_soup_from_page(paginated_url)
|
||||
soup = self._get_soup_from_page(paginated_url)
|
||||
|
||||
pinned_giveaway_count = len(soup.select('div.pinned-giveaways__outer-wrap div.giveaway__row-inner-wrap'))
|
||||
all_games_list_count = len(soup.select('div.giveaway__row-inner-wrap'))
|
||||
|
@ -181,17 +201,17 @@ class EnterGiveaways:
|
|||
break
|
||||
|
||||
for item in unentered_game_list:
|
||||
giveaway = Giveaway(item)
|
||||
giveaway = GiveawayEntry(item)
|
||||
txt = f"〰 {giveaway.game_name} - {giveaway.cost}P - {giveaway.game_entries} entries " \
|
||||
f"(w/ {giveaway.copies} copies) - Created {giveaway.time_created_string} ago " \
|
||||
f"with {giveaway.time_remaining_string} remaining by {giveaway.user}."
|
||||
logger.info(txt)
|
||||
if giveaway.pinned and not self.pinned:
|
||||
if giveaway.pinned and not self._pinned:
|
||||
logger.info(f"〰️ Giveaway {giveaway.game_name} is pinned. Ignoring.")
|
||||
continue
|
||||
|
||||
if self.points == 0 or self.points < self.min_points:
|
||||
txt = f"🟡 We have {self.points} points, but we need {self.min_points} to start."
|
||||
if self._points == 0 or self._points < self._min_points:
|
||||
txt = f"🟡 We have {self._points} points, but we need {self._min_points} to start."
|
||||
logger.info(txt)
|
||||
run = False
|
||||
break
|
||||
|
@ -199,37 +219,26 @@ class EnterGiveaways:
|
|||
if not giveaway.cost:
|
||||
logger.error(f"Cost could not be determined for '{giveaway.game_name}'")
|
||||
continue
|
||||
if_enter_giveaway = self.should_we_enter_giveaway(giveaway)
|
||||
if_enter_giveaway = self._should_we_enter_giveaway(giveaway)
|
||||
if if_enter_giveaway:
|
||||
res = self.enter_giveaway(giveaway)
|
||||
res = self._enter_giveaway(giveaway)
|
||||
if res:
|
||||
GiveawayHelper.upsert_giveaway(giveaway, True)
|
||||
self.points -= int(giveaway.cost)
|
||||
GiveawayHelper.upsert_giveaway_with_details(giveaway, True, False)
|
||||
self._points -= int(giveaway.cost)
|
||||
txt = f"✅ Entered giveaway '{giveaway.game_name}'"
|
||||
logger.info(txt)
|
||||
sleep(randint(4, 15))
|
||||
else:
|
||||
GiveawayHelper.upsert_giveaway(giveaway, False)
|
||||
GiveawayHelper.upsert_giveaway_with_details(giveaway, False, False)
|
||||
else:
|
||||
GiveawayHelper.upsert_giveaway(giveaway, False)
|
||||
GiveawayHelper.upsert_giveaway(giveaway)
|
||||
# if we are on any filter type except New and we get to a giveaway that exceeds our
|
||||
# max time left amount, then we don't need to continue to look at giveaways as any
|
||||
# after this point will also exceed the max time left
|
||||
if self.gifts_type != "New" and not giveaway.pinned and \
|
||||
giveaway.time_remaining_in_minutes > self.max_time_left:
|
||||
if self._gifts_type != "New" and not giveaway.pinned and \
|
||||
giveaway.time_remaining_in_minutes > self._max_time_left:
|
||||
logger.info("🟡 We have run out of gifts to consider.")
|
||||
run = False
|
||||
break
|
||||
|
||||
n = n + 1
|
||||
|
||||
def start(self):
|
||||
self.update_info()
|
||||
if self.points >= self.min_points:
|
||||
txt = f"〰 You have {self.points} points. Evaluating '{self.gifts_type}' giveaways..."
|
||||
logger.info(txt)
|
||||
self.evaluate_giveaways()
|
||||
else:
|
||||
txt = f"🟡 You have {self.points} points which is below your minimum point threshold of " \
|
||||
f"{self.min_points} points for '{self.gifts_type}' giveaways. Not evaluating right now."
|
||||
logger.info(txt)
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from requests.adapters import HTTPAdapter
|
||||
from urllib3.util import Retry
|
||||
|
||||
from .database import GiveawayHelper
|
||||
from .log import get_logger
|
||||
from .won_entry import WonEntry
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class SteamGiftsException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class EvaluateWonGiveaways:
|
||||
|
||||
def __init__(self, cookie, user_agent, notification):
|
||||
self._contributor_level = None
|
||||
self._xsrf_token = None
|
||||
self._cookie = {
|
||||
'PHPSESSID': cookie
|
||||
}
|
||||
self._user_agent = user_agent
|
||||
self._notification = notification
|
||||
|
||||
self._base = "https://www.steamgifts.com/giveaways/won"
|
||||
self._session = requests.Session()
|
||||
|
||||
def start(self):
|
||||
self._evaluate_won_giveaways()
|
||||
|
||||
def _requests_retry_session(
|
||||
self,
|
||||
retries=5,
|
||||
backoff_factor=0.3
|
||||
):
|
||||
session = self._session or requests.Session()
|
||||
retry = Retry(
|
||||
total=retries,
|
||||
read=retries,
|
||||
connect=retries,
|
||||
backoff_factor=backoff_factor,
|
||||
status_forcelist=(500, 502, 504),
|
||||
)
|
||||
adapter = HTTPAdapter(max_retries=retry)
|
||||
session.mount('http://', adapter)
|
||||
session.mount('https://', adapter)
|
||||
return session
|
||||
|
||||
def _get_soup_from_page(self, url):
|
||||
headers = {
|
||||
'User-Agent': self._user_agent
|
||||
}
|
||||
self._requests_retry_session().get(url, headers=headers)
|
||||
r = requests.get(url, cookies=self._cookie)
|
||||
soup = BeautifulSoup(r.text, 'html.parser')
|
||||
return soup
|
||||
|
||||
def _evaluate_won_giveaways(self, page=1):
|
||||
soup = self._get_soup_from_page(self._base)
|
||||
|
||||
try:
|
||||
self._xsrf_token = soup.find('input', {'name': 'xsrf_token'})['value']
|
||||
self._points = int(soup.find('span', {'class': 'nav__points'}).text) # storage points
|
||||
self._contributor_level = int(float(soup.select_one('nav a>span[title]')['title']))
|
||||
except TypeError:
|
||||
logger.error("⛔⛔⛔ Cookie is not valid. A new one must be added.⛔⛔⛔")
|
||||
raise SteamGiftsException("Cookie is not valid. A new one must be added.")
|
||||
|
||||
won_game_list = soup.select('div[class=table__row-inner-wrap]')
|
||||
|
||||
if not len(won_game_list):
|
||||
txt = f"🟡 No won games to evaluate"
|
||||
logger.info(txt)
|
||||
|
||||
for item in won_game_list:
|
||||
won_giveaway = WonEntry(item)
|
||||
w = GiveawayHelper.get_by_giveaway_id(won_giveaway.giveaway_game_id)
|
||||
logger.debug(f"Giveaway in db: {w}")
|
||||
if w and not w.won and w.entered:
|
||||
logger.info(f"Marking {won_giveaway.game_name} as won.")
|
||||
logger.debug(f"Marking: {w}")
|
||||
GiveawayHelper.mark_game_as_won(won_giveaway.giveaway_game_id)
|
||||
|
|
@ -5,7 +5,7 @@ import time
|
|||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class Giveaway:
|
||||
class GiveawayEntry:
|
||||
|
||||
def __init__(self, soup_item):
|
||||
self.steam_app_id = None
|
||||
|
@ -29,27 +29,27 @@ class Giveaway:
|
|||
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)
|
||||
self.steam_app_id = self._get_steam_app_id(self.steam_url)
|
||||
self.game_name = soup_item.find('a', {'class': 'giveaway__heading__name'}).text
|
||||
self.giveaway_game_id = soup_item.find('a', {'class': 'giveaway__heading__name'})['href'].split('/')[2]
|
||||
self.giveaway_uri = soup_item.select_one('a.giveaway__heading__name')['href']
|
||||
pin_class = soup_item.parent.parent.get("class")
|
||||
self.pinned = pin_class is not None and len(pin_class) > 0 and pin_class[0].find('pinned') != -1
|
||||
self.cost, self.copies = self.determine_cost_and_copies(soup_item, self.game_name, self.giveaway_game_id)
|
||||
self.cost, self.copies = self._determine_cost_and_copies(soup_item, self.game_name, self.giveaway_game_id)
|
||||
self.game_entries = int(soup_item.select('div.giveaway__links span')[0].text.split(' ')[0].replace(',', ''))
|
||||
contributor_level = soup_item.select_one('div[title="Contributor Level"]')
|
||||
self.contributor_level = self.determine_contributor_level(contributor_level)
|
||||
self.contributor_level = self._determine_contributor_level(contributor_level)
|
||||
self.user = soup_item.select_one('a.giveaway__username').text
|
||||
times = soup_item.select('div span[data-timestamp]')
|
||||
self.time_remaining_timestamp = int(times[0]['data-timestamp'])
|
||||
self.time_remaining_string = times[0].text
|
||||
self.time_remaining_in_minutes = self.determine_time_in_minutes(times[0]['data-timestamp'])
|
||||
self.time_remaining_in_minutes = self._determine_time_in_minutes(times[0]['data-timestamp'])
|
||||
self.time_created_timestamp = int(times[1]['data-timestamp'])
|
||||
self.time_created_string = times[1].text
|
||||
self.time_created_in_minutes = self.determine_time_in_minutes(times[1]['data-timestamp'])
|
||||
self.time_created_in_minutes = self._determine_time_in_minutes(times[1]['data-timestamp'])
|
||||
logger.debug(f"Scraped Giveaway: {self}")
|
||||
|
||||
def determine_contributor_level(self, contributor_level):
|
||||
def _determine_contributor_level(self, contributor_level):
|
||||
if contributor_level is None:
|
||||
return 0
|
||||
match = re.search('^Level (?P<level>[0-9]+)\\+$', contributor_level.text, re.IGNORECASE)
|
||||
|
@ -58,14 +58,14 @@ class Giveaway:
|
|||
else:
|
||||
return None
|
||||
|
||||
def get_steam_app_id(self, steam_url):
|
||||
def _get_steam_app_id(self, steam_url):
|
||||
match = re.search('^.+/[a-z0-9]+/(?P<steam_app_id>[0-9]+)/$', steam_url, re.IGNORECASE)
|
||||
if match:
|
||||
return match.group('steam_app_id')
|
||||
else:
|
||||
return None
|
||||
|
||||
def determine_time_in_minutes(self, timestamp):
|
||||
def _determine_time_in_minutes(self, timestamp):
|
||||
if not timestamp or not re.search('^[0-9]+$', timestamp):
|
||||
logger.error(f"Could not determine time from string {timestamp}")
|
||||
return None
|
||||
|
@ -73,7 +73,7 @@ class Giveaway:
|
|||
giveaway_endtime = time.localtime(int(timestamp))
|
||||
return int(abs((time.mktime(giveaway_endtime) - time.mktime(now)) / 60))
|
||||
|
||||
def determine_cost_and_copies(self, item, game_name, game_id):
|
||||
def _determine_cost_and_copies(self, item, game_name, game_id):
|
||||
item_headers = item.find_all('span', {'class': 'giveaway__heading__thin'})
|
||||
if len(item_headers) == 1: # then no multiple copies
|
||||
game_cost = item_headers[0].getText().replace('(', '').replace(')', '').replace('P', '')
|
|
@ -1,14 +1,13 @@
|
|||
import datetime
|
||||
import threading
|
||||
from datetime import timedelta, datetime
|
||||
from random import randint
|
||||
from threading import Thread
|
||||
from time import sleep
|
||||
|
||||
from dateutil import tz
|
||||
|
||||
from .log import get_logger
|
||||
from .enter_giveaways import EnterGiveaways
|
||||
from .evaluate_won_giveaways import EvaluateWonGiveaways
|
||||
from .log import get_logger
|
||||
from .scheduler import Scheduler
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
@ -20,52 +19,72 @@ class GiveawayThread(threading.Thread):
|
|||
self.exc = None
|
||||
self.config = config
|
||||
self.notification = notification
|
||||
logger.debug("Creating scheduler")
|
||||
self._scheduler = Scheduler()
|
||||
self.won_giveaway_job_id = 'eval_won_giveaways'
|
||||
self.evaluate_giveaway_job_id = 'eval_giveaways'
|
||||
|
||||
def run_steam_gifts(self, config, notification):
|
||||
cookie = config['DEFAULT'].get('cookie')
|
||||
user_agent = config['DEFAULT'].get('user_agent')
|
||||
main_page_enabled = config['DEFAULT'].getboolean('enabled')
|
||||
minimum_points = config['DEFAULT'].getint('minimum_points')
|
||||
max_entries = config['DEFAULT'].getint('max_entries')
|
||||
max_time_left = config['DEFAULT'].getint('max_time_left')
|
||||
minimum_game_points = config['DEFAULT'].getint('minimum_game_points')
|
||||
blacklist = config['DEFAULT'].get('blacklist_keywords')
|
||||
if config['DEFAULT'].getboolean('enabled'):
|
||||
cookie = config['DEFAULT'].get('cookie')
|
||||
user_agent = config['DEFAULT'].get('user_agent')
|
||||
|
||||
all_page = EnterGiveaways(cookie, user_agent, 'All', False, minimum_points, max_entries,
|
||||
max_time_left, minimum_game_points, blacklist, notification)
|
||||
minimum_points = config['DEFAULT'].getint('minimum_points')
|
||||
max_entries = config['DEFAULT'].getint('max_entries')
|
||||
max_time_left = config['DEFAULT'].getint('max_time_left')
|
||||
minimum_game_points = config['DEFAULT'].getint('minimum_game_points')
|
||||
blacklist = config['DEFAULT'].get('blacklist_keywords')
|
||||
|
||||
wishlist_page_enabled = config['WISHLIST'].getboolean('wishlist.enabled')
|
||||
wishlist_minimum_points = config['WISHLIST'].getint('wishlist.minimum_points')
|
||||
wishlist_max_entries = config['WISHLIST'].getint('wishlist.max_entries')
|
||||
wishlist_max_time_left = config['WISHLIST'].getint('wishlist.max_time_left')
|
||||
self._all_page = EnterGiveaways(cookie, user_agent, 'All', False, minimum_points, max_entries,
|
||||
max_time_left, minimum_game_points, blacklist, notification)
|
||||
|
||||
wishlist_page = EnterGiveaways(cookie, user_agent, 'Wishlist', False, wishlist_minimum_points,
|
||||
wishlist_max_entries, wishlist_max_time_left, 0, '', notification)
|
||||
if config['WISHLIST'].getboolean('wishlist.enabled'):
|
||||
wishlist_minimum_points = config['WISHLIST'].getint('wishlist.minimum_points')
|
||||
wishlist_max_entries = config['WISHLIST'].getint('wishlist.max_entries')
|
||||
wishlist_max_time_left = config['WISHLIST'].getint('wishlist.max_time_left')
|
||||
|
||||
if not main_page_enabled and not wishlist_page_enabled:
|
||||
self._wishlist_page = EnterGiveaways(cookie, user_agent, 'Wishlist', False, wishlist_minimum_points,
|
||||
wishlist_max_entries, wishlist_max_time_left, 0, '', notification)
|
||||
|
||||
if not self._all_page and not self._wishlist_page:
|
||||
logger.error("⁉️ Both 'Default' and 'Wishlist' configurations are disabled. Nothing will run. Exiting...")
|
||||
sleep(10)
|
||||
exit(-1)
|
||||
|
||||
while True:
|
||||
logger.info("🟢 Evaluating giveaways.")
|
||||
if wishlist_page_enabled:
|
||||
wishlist_page.start()
|
||||
if main_page_enabled:
|
||||
all_page.start()
|
||||
won_giveaway_job = self._scheduler.get_job(job_id=self.won_giveaway_job_id)
|
||||
if won_giveaway_job:
|
||||
logger.debug("Previous won giveaway evaluator job exists. Removing.")
|
||||
won_giveaway_job.remove()
|
||||
won_runner = GiveawayThread.WonRunner(EvaluateWonGiveaways(cookie, user_agent, notification),
|
||||
self.won_giveaway_job_id)
|
||||
self._scheduler.add_job(won_runner.run,
|
||||
id=self.won_giveaway_job_id,
|
||||
trigger='interval',
|
||||
max_instances=1,
|
||||
replace_existing=True,
|
||||
hours=12,
|
||||
next_run_time=datetime.now() + timedelta(minutes=5),
|
||||
jitter=8000)
|
||||
|
||||
logger.info("🔴 All giveaways evaluated.")
|
||||
random_seconds = randint(1740, 3540) # sometime between 29-59 minutes
|
||||
when_to_start_again = datetime.now(tz=tz.tzlocal()) + timedelta(seconds=random_seconds)
|
||||
logger.info(f"🛋 Going to sleep for {random_seconds / 60} minutes. "
|
||||
f"Will start again at {when_to_start_again}")
|
||||
sleep(random_seconds)
|
||||
evaluate_giveaway_job = self._scheduler.get_job(job_id=self.evaluate_giveaway_job_id)
|
||||
if evaluate_giveaway_job:
|
||||
logger.debug("Previous giveaway evaluator job exists. Removing.")
|
||||
evaluate_giveaway_job.remove()
|
||||
runner = GiveawayThread.GiveawayRunner(self._wishlist_page, self._all_page,
|
||||
self.evaluate_giveaway_job_id)
|
||||
self._scheduler.add_job(runner.run,
|
||||
id=self.evaluate_giveaway_job_id,
|
||||
trigger='interval',
|
||||
max_instances=1,
|
||||
replace_existing=True,
|
||||
minutes=44,
|
||||
next_run_time=datetime.now(),
|
||||
jitter=900)
|
||||
|
||||
def run(self):
|
||||
# Variable that stores the exception, if raised by someFunction
|
||||
self.exc = None
|
||||
try:
|
||||
self.run_steam_gifts(self.config, self.notification)
|
||||
self._scheduler.start()
|
||||
except BaseException as e:
|
||||
self.exc = e
|
||||
|
||||
|
@ -76,3 +95,43 @@ class GiveawayThread(threading.Thread):
|
|||
# if any was caught
|
||||
if self.exc:
|
||||
raise self.exc
|
||||
|
||||
class WonRunner:
|
||||
def __init__(self, won_page, job_id):
|
||||
self._won_page = won_page
|
||||
self._job_id = job_id
|
||||
|
||||
def run(self):
|
||||
logger.info(" 💍 Evaluating won giveaways. 💍")
|
||||
if self._won_page:
|
||||
self._won_page.start()
|
||||
logger.info(" 💍 All won giveaways evaluated. 💍")
|
||||
scheduler = Scheduler()
|
||||
evaluate_giveaway_job = scheduler.get_job(job_id=self._job_id)
|
||||
if evaluate_giveaway_job:
|
||||
when_to_start_again = evaluate_giveaway_job.next_run_time
|
||||
logger.info(f"💍 Going to sleep. Will start again at {when_to_start_again}")
|
||||
else:
|
||||
logger.info("No set time to evaluate won giveaways again.")
|
||||
|
||||
class GiveawayRunner:
|
||||
|
||||
def __init__(self, wishlist_page, all_page, job_id):
|
||||
self._wishlist_page = wishlist_page
|
||||
self._all_page = all_page
|
||||
self._job_id = job_id
|
||||
|
||||
def run(self):
|
||||
logger.info("🟢 Evaluating giveaways.")
|
||||
if self._wishlist_page:
|
||||
self._wishlist_page.start()
|
||||
if self._all_page:
|
||||
self._all_page.start()
|
||||
logger.info("🔴 All giveaways evaluated.")
|
||||
scheduler = Scheduler()
|
||||
evaluate_giveaway_job = scheduler.get_job(job_id=self._job_id)
|
||||
if evaluate_giveaway_job:
|
||||
when_to_start_again = evaluate_giveaway_job.next_run_time
|
||||
logger.info(f"🛋 Going to sleep. Will start again at {when_to_start_again}")
|
||||
else:
|
||||
logger.info("No set time to evaluate giveaways again.")
|
|
@ -13,11 +13,15 @@ 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())
|
||||
|
||||
__mapper_args__ = {"eager_defaults": True}
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__class__) + ": " + str(self.__dict__)
|
||||
|
||||
|
||||
class TableSteamItem(Base):
|
||||
__tablename__ = 'steam_item'
|
||||
|
@ -29,6 +33,9 @@ class TableSteamItem(Base):
|
|||
|
||||
giveaways = relationship("TableGiveaway", back_populates="steam_item")
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__class__) + ": " + str(self.__dict__)
|
||||
|
||||
|
||||
class TableGiveaway(Base):
|
||||
__tablename__ = 'giveaway'
|
||||
|
@ -42,6 +49,7 @@ 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())
|
||||
|
@ -49,3 +57,6 @@ class TableGiveaway(Base):
|
|||
steam_item = relationship("TableSteamItem", back_populates="giveaways")
|
||||
|
||||
__mapper_args__ = {"eager_defaults": True}
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__class__) + ": " + str(self.__dict__)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import os
|
||||
|
||||
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
|
||||
from apscheduler.schedulers.blocking import BlockingScheduler
|
||||
|
||||
from .log import get_logger
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class Scheduler:
|
||||
class __Scheduler:
|
||||
def __init__(self):
|
||||
jobstores = {
|
||||
'default': SQLAlchemyJobStore(url=f"{os.getenv('BOT_DB_URL', 'sqlite:///./config/sqlite.db')}")
|
||||
}
|
||||
job_defaults = {
|
||||
'coalesce': True,
|
||||
'max_instances': 3
|
||||
}
|
||||
self.scheduler = BlockingScheduler(jobstores=jobstores, job_defaults=job_defaults)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.scheduler, name)
|
||||
|
||||
instance = None
|
||||
|
||||
def __init__(self):
|
||||
if not Scheduler.instance:
|
||||
Scheduler.instance = Scheduler.__Scheduler()
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self.instance, name)
|
|
@ -0,0 +1,22 @@
|
|||
import re
|
||||
from .log import get_logger
|
||||
import time
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
||||
class WonEntry:
|
||||
|
||||
def __init__(self, soup_item):
|
||||
self.game_name = None
|
||||
self.giveaway_game_id = None
|
||||
self.giveaway_uri = None
|
||||
|
||||
logger.debug(f"Won Giveaway html: {soup_item}")
|
||||
self.game_name = soup_item.find('a', {'class': 'table__column__heading'}).text
|
||||
self.giveaway_game_id = soup_item.find('a', {'class': 'table__column__heading'})['href'].split('/')[2]
|
||||
self.giveaway_uri = soup_item.select_one('a.table__column__heading')['href']
|
||||
logger.debug(f"Scraped Won Giveaway: {self}")
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__class__) + ": " + str(self.__dict__)
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{% block title %} {% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1 class="logo">Steamgifts Bot</h1>
|
||||
<strong><nav>
|
||||
<ul class="menu">
|
||||
<li><a href="{{ url_for('config') }}">Config</a></li>
|
||||
<li><a href="{{ url_for('stats') }}">Stats</a></li>
|
||||
<li><a href="{{ url_for('log_info') }}">Info Logs</a></li>
|
||||
<li><a href="{{ url_for('log_debug') }}">Debug Logs</a></li>
|
||||
<li><a href="{{ url_for('db_giveaways') }}">Giveaways</a></li>
|
||||
<li><a href="{{ url_for('db_notifications') }}">Notifications</a></li>
|
||||
</ul>
|
||||
</nav></strong>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container">
|
||||
{% block content %} {% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,26 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{name}} Steamgifts Bot Configuration</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1 class="logo">Steamgifts Bot</h1>
|
||||
<strong><nav>
|
||||
<ul class="menu">
|
||||
<li><a href="{{ url_for('config') }}">Config</a></li>
|
||||
<li><a href="{{ url_for('log_info') }}">Info Logs</a></li>
|
||||
<li><a href="{{ url_for('log_debug') }}">Debug Logs</a></li>
|
||||
<li><a href="{{ url_for('db_giveaways') }}">Giveaways</a></li>
|
||||
<li><a href="{{ url_for('db_notifications') }}">Notifications</a></li>
|
||||
</ul>
|
||||
</nav></strong>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container">
|
||||
<pre>{{content}}</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} {{name}} Steamgifts Bot Configuration {% endblock %}
|
||||
{% block content %}
|
||||
<pre>{{data}}</pre>
|
||||
{% endblock %}
|
|
@ -1,28 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{name}} Steamgifts Bot DB View</title>
|
||||
<link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1 class="logo">Steamgifts Bot</h1>
|
||||
<strong>
|
||||
<nav>
|
||||
<ul class="menu">
|
||||
<li><a href="{{ url_for('config') }}">Config</a></li>
|
||||
<li><a href="{{ url_for('log_info') }}">Info Logs</a></li>
|
||||
<li><a href="{{ url_for('log_debug') }}">Debug Logs</a></li>
|
||||
<li><a href="{{ url_for('db_giveaways') }}">Giveaways</a></li>
|
||||
<li><a href="{{ url_for('db_notifications') }}">Notifications</a></li>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} {{name}} Steamgifts Bot Giveaways {% endblock %}
|
||||
{% block content %}
|
||||
<ul class="pagination">
|
||||
{% if data.previous_page %}
|
||||
<li class="page-item"> <a class="page-link" href="{{ url_for('db_giveaways', page=data.previous_page) }}">Previous</a></li>
|
||||
{% else %}
|
||||
<li class="page-item">Previous</li>
|
||||
{% endif %}
|
||||
{% if data.next_page %}
|
||||
<li class="page-item"> <a class="page-link" href="{{ url_for('db_giveaways', page=data.next_page) }}">Next</a></li>
|
||||
{% else %}
|
||||
<li class="page-item">Next</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</strong>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container">
|
||||
<table>
|
||||
<table class="styled-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Giveaway ID</th>
|
||||
|
@ -40,7 +32,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in content %}
|
||||
{% for row in data.items %}
|
||||
<tr>
|
||||
<td>{{row.giveaway_id}}</td>
|
||||
<td>{{row.steam_id}}</td>
|
||||
|
@ -58,6 +50,4 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
|
@ -1,26 +1,6 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{name}} Steamgifts Bot {{log_type}} Logs</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1 class="logo">Steamgifts Bot</h1>
|
||||
<strong><nav>
|
||||
<ul class="menu">
|
||||
<li><a href="{{ url_for('config') }}">Config</a></li>
|
||||
<li><a href="{{ url_for('log_info') }}">Info Logs</a></li>
|
||||
<li><a href="{{ url_for('log_debug') }}">Debug Logs</a></li>
|
||||
<li><a href="{{ url_for('db_giveaways') }}">Giveaways</a></li>
|
||||
<li><a href="{{ url_for('db_notifications') }}">Notifications</a></li>
|
||||
</ul>
|
||||
</nav></strong>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container">
|
||||
<pre id="output">{{content}}</pre>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} {{name}} Steamgifts Bot {{log_type}} Logs{% endblock %}
|
||||
{% block content %}
|
||||
<pre id="output">{{data}}</pre>
|
||||
{% endblock %}
|
|
@ -1,28 +1,8 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{name}} Steamgifts Bot DB View</title>
|
||||
<link href="{{ url_for('static', filename='css/main.css') }}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1 class="logo">Steamgifts Bot</h1>
|
||||
<strong>
|
||||
<nav>
|
||||
<ul class="menu">
|
||||
<li><a href="{{ url_for('config') }}">Config</a></li>
|
||||
<li><a href="{{ url_for('log_info') }}">Info Logs</a></li>
|
||||
<li><a href="{{ url_for('log_debug') }}">Debug Logs</a></li>
|
||||
<li><a href="{{ url_for('db_giveaways') }}">Giveaways</a></li>
|
||||
<li><a href="{{ url_for('db_notifications') }}">Notifications</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</strong>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container">
|
||||
<table>
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} {{name}} Steamgifts Bot Notifications {% endblock %}
|
||||
{% block content %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
|
@ -34,7 +14,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for row in content %}
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
<td>{{row.id}}</td>
|
||||
<td>{{row.medium}}</td>
|
||||
|
@ -46,6 +26,4 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
|
@ -0,0 +1,10 @@
|
|||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %} {{name}} Steamgifts Bot Configuration {% endblock %}
|
||||
{% block content %}
|
||||
<ul>
|
||||
<li>Total Giveaways Considered: {{totals}}</li>
|
||||
<li>Giveaways Entered: {{entered}}</li>
|
||||
<li>Giveaways Won (with the bot): {{won}}</li>
|
||||
</ul>
|
||||
{% endblock %}
|
|
@ -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')
|
||||
|
@ -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.name, 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.name, 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.name, 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,11 +63,19 @@ 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, data=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/<int:page>", methods=['GET'])
|
||||
def db_giveaways(page):
|
||||
return render_template('giveaways.html', name=self.prefix, data=GiveawayHelper.paginate(page=page))
|
||||
|
||||
@app.route(f"{self.app_root}stats")
|
||||
def stats():
|
||||
totals = GiveawayHelper.total_giveaways()
|
||||
entered = GiveawayHelper.total_entered()
|
||||
won = GiveawayHelper.total_won()
|
||||
return render_template('stats.html', name=self.prefix, totals=totals, entered=entered, won=won)
|
||||
|
||||
if self.enabled:
|
||||
logger.info("Webserver Enabled. Running")
|
||||
|
|
Loading…
Reference in New Issue