staging into master #426
@ -17,6 +17,7 @@
|
||||
"permission": "src/modules/permission/permission.json",
|
||||
"technician": "src/modules/technician/technician.json",
|
||||
"short-role-name": "src/modules/short_role_name/short-role-name.json",
|
||||
"steam-special-offers": "src/modules/steam_special_offers/steam-special-offers.json",
|
||||
"checks": "tools/checks/checks.json",
|
||||
"get-version": "tools/get_version/get-version.json",
|
||||
"post-build": "tools/post_build/post-build.json",
|
||||
|
@ -69,6 +69,7 @@
|
||||
"../modules/level/level.json",
|
||||
"../modules/permission/permission.json",
|
||||
"../modules/short_role_name/short-role-name.json",
|
||||
"../modules/steam_special_offers/steam-special-offers.json",
|
||||
"../modules/technician/technician.json"
|
||||
]
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 23eafb2e211241acbbc52833d139c67f1ecc69f5
|
||||
Subproject commit 839bbb823a6e0c9e1443e0844446bfe240284063
|
@ -14,6 +14,7 @@ from modules.database.database_module import DatabaseModule
|
||||
from modules.level.level_module import LevelModule
|
||||
from modules.permission.permission_module import PermissionModule
|
||||
from modules.short_role_name.short_role_name_module import ShortRoleNameModule
|
||||
from modules.steam_special_offers.special_offers_module import SteamSpecialOffersModule
|
||||
from modules.technician.technician_module import TechnicianModule
|
||||
|
||||
|
||||
@ -37,6 +38,7 @@ class ModuleList:
|
||||
TechnicianModule,
|
||||
AchievementsModule,
|
||||
ShortRoleNameModule,
|
||||
SteamSpecialOffersModule,
|
||||
# has to be last!
|
||||
BootLogModule,
|
||||
CoreExtensionModule,
|
||||
|
@ -16,6 +16,7 @@ from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||
from bot_core.logging.command_logger import CommandLogger
|
||||
from bot_core.logging.database_logger import DatabaseLogger
|
||||
from bot_core.logging.message_logger import MessageLogger
|
||||
from bot_core.logging.task_logger import TaskLogger
|
||||
from bot_data.db_context import DBContext
|
||||
|
||||
|
||||
@ -43,6 +44,7 @@ class Startup(StartupABC):
|
||||
services.add_singleton(CustomFileLoggerABC, CommandLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, MessageLogger)
|
||||
services.add_singleton(CustomFileLoggerABC, TaskLogger)
|
||||
|
||||
if self._feature_flags.get_flag(FeatureFlagsEnum.api_module):
|
||||
services.add_singleton(CustomFileLoggerABC, ApiLogger)
|
||||
|
@ -17,6 +17,7 @@ class FeatureFlagsEnum(Enum):
|
||||
moderator_module = "ModeratorModule"
|
||||
permission_module = "PermissionModule"
|
||||
short_role_name_module = "ShortRoleNameModule"
|
||||
steam_special_offers_module = "SteamSpecialOffersModule"
|
||||
# features
|
||||
api_only = "ApiOnly"
|
||||
presence = "Presence"
|
||||
@ -25,3 +26,4 @@ class FeatureFlagsEnum(Enum):
|
||||
sync_xp = "SyncXp"
|
||||
short_role_name = "ShortRoleName"
|
||||
technician_full_access = "TechnicianFullAccess"
|
||||
steam_special_offers = "SteamSpecialOffers"
|
||||
|
@ -19,6 +19,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
|
||||
FeatureFlagsEnum.permission_module.value: True, # 02.10.2022 #48
|
||||
FeatureFlagsEnum.config_module.value: True, # 19.07.2023 #127
|
||||
FeatureFlagsEnum.short_role_name_module.value: True, # 28.09.2023 #378
|
||||
FeatureFlagsEnum.steam_special_offers_module.value: True, # 11.10.2023 #188
|
||||
# features
|
||||
FeatureFlagsEnum.api_only.value: False, # 13.10.2022 #70
|
||||
FeatureFlagsEnum.presence.value: True, # 03.10.2022 #56
|
||||
@ -27,6 +28,7 @@ class FeatureFlagsSettings(ConfigurationModelABC):
|
||||
FeatureFlagsEnum.sync_xp.value: False, # 25.09.2023 #366
|
||||
FeatureFlagsEnum.short_role_name.value: False, # 28.09.2023 #378
|
||||
FeatureFlagsEnum.technician_full_access.value: False, # 03.10.2023 #393
|
||||
FeatureFlagsEnum.steam_special_offers.value: False, # 11.10.2023 #188
|
||||
}
|
||||
|
||||
def __init__(self, **kwargs: dict):
|
||||
|
15
kdb-bot/src/bot_core/logging/task_logger.py
Normal file
15
kdb-bot/src/bot_core/logging/task_logger.py
Normal file
@ -0,0 +1,15 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_core.time import TimeFormatSettings
|
||||
|
||||
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
|
||||
|
||||
|
||||
class TaskLogger(CustomFileLoggerABC):
|
||||
def __init__(
|
||||
self,
|
||||
config: ConfigurationABC,
|
||||
time_format: TimeFormatSettings,
|
||||
env: ApplicationEnvironmentABC,
|
||||
):
|
||||
CustomFileLoggerABC.__init__(self, "Task", config, time_format, env)
|
1
kdb-bot/src/modules/steam_special_offers/__init__.py
Normal file
1
kdb-bot/src/modules/steam_special_offers/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# imports
|
@ -0,0 +1 @@
|
||||
# imports
|
@ -0,0 +1,13 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from discord.ext import commands
|
||||
|
||||
|
||||
class SpecialOfferWatcherABC(commands.Cog):
|
||||
@abstractmethod
|
||||
def __init__(self):
|
||||
commands.Cog.__init__(self)
|
||||
|
||||
@abstractmethod
|
||||
def start(self):
|
||||
pass
|
@ -0,0 +1 @@
|
||||
# imports
|
@ -0,0 +1,25 @@
|
||||
from cpl_core.logging import LoggerABC
|
||||
from cpl_discord.events import OnReadyABC
|
||||
from cpl_discord.service import DiscordBotServiceABC
|
||||
|
||||
from bot_core.logging.task_logger import TaskLogger
|
||||
from modules.steam_special_offers.base.special_offer_watcher_abc import SpecialOfferWatcherABC
|
||||
|
||||
|
||||
class SpecialOfferOnReadyEvent(OnReadyABC):
|
||||
def __init__(
|
||||
self,
|
||||
logger: TaskLogger,
|
||||
bot: DiscordBotServiceABC,
|
||||
watchers: list[SpecialOfferWatcherABC],
|
||||
):
|
||||
OnReadyABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
self._bot = bot
|
||||
self._watchers = watchers
|
||||
|
||||
async def on_ready(self):
|
||||
for watcher in self._watchers:
|
||||
self._logger.info(__name__, f"Starting watcher {type(watcher).__name__}")
|
||||
watcher.start()
|
@ -0,0 +1 @@
|
||||
# imports
|
@ -0,0 +1,6 @@
|
||||
class GameOffer:
|
||||
def __init__(self, name: str, original_price: float, discount_price: float, discount_pct: int):
|
||||
self.name = name
|
||||
self.original_price = original_price
|
||||
self.discount_price = discount_price
|
||||
self.discount_pct = discount_pct
|
46
kdb-bot/src/modules/steam_special_offers/special-offers.json
Normal file
46
kdb-bot/src/modules/steam_special_offers/special-offers.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"ProjectSettings": {
|
||||
"Name": "steam-special-offers",
|
||||
"Version": {
|
||||
"Major": "0",
|
||||
"Minor": "0",
|
||||
"Micro": "0"
|
||||
},
|
||||
"Author": "",
|
||||
"AuthorEmail": "",
|
||||
"Description": "",
|
||||
"LongDescription": "",
|
||||
"URL": "",
|
||||
"CopyrightDate": "",
|
||||
"CopyrightName": "",
|
||||
"LicenseName": "",
|
||||
"LicenseDescription": "",
|
||||
"Dependencies": [
|
||||
"cpl-core>=2023.4.0.post5"
|
||||
],
|
||||
"DevDependencies": [
|
||||
"cpl-cli>=2023.4.0.post3"
|
||||
],
|
||||
"PythonVersion": ">=3.10.4",
|
||||
"PythonPath": {
|
||||
"linux": ""
|
||||
},
|
||||
"Classifiers": []
|
||||
},
|
||||
"BuildSettings": {
|
||||
"ProjectType": "library",
|
||||
"SourcePath": "",
|
||||
"OutputPath": "../../dist",
|
||||
"Main": "steam_special_offers.main",
|
||||
"EntryPoint": "steam-special-offers",
|
||||
"IncludePackageData": false,
|
||||
"Included": [],
|
||||
"Excluded": [
|
||||
"*/__pycache__",
|
||||
"*/logs",
|
||||
"*/tests"
|
||||
],
|
||||
"PackageData": {},
|
||||
"ProjectReferences": []
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
from cpl_core.configuration import ConfigurationABC
|
||||
from cpl_core.dependency_injection import ServiceCollectionABC
|
||||
from cpl_core.environment import ApplicationEnvironmentABC
|
||||
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
|
||||
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
|
||||
|
||||
from bot_core.abc.module_abc import ModuleABC
|
||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||
from modules.steam_special_offers.base.special_offer_watcher_abc import SpecialOfferWatcherABC
|
||||
from modules.steam_special_offers.events.special_offer_on_ready_event import SpecialOfferOnReadyEvent
|
||||
from modules.steam_special_offers.steam_offer_watcher import SteamOfferWatcher
|
||||
|
||||
|
||||
class SteamSpecialOffersModule(ModuleABC):
|
||||
def __init__(self, dc: DiscordCollectionABC):
|
||||
ModuleABC.__init__(self, dc, FeatureFlagsEnum.steam_special_offers_module)
|
||||
|
||||
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
|
||||
pass
|
||||
|
||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||
services.add_singleton(SpecialOfferWatcherABC, SteamOfferWatcher)
|
||||
# commands
|
||||
# events
|
||||
self._dc.add_event(DiscordEventTypesEnum.on_ready.value, SpecialOfferOnReadyEvent)
|
@ -0,0 +1,88 @@
|
||||
import bs4
|
||||
import requests
|
||||
from cpl_query.extension import List
|
||||
from discord.ext import tasks
|
||||
|
||||
from bot_core.logging.task_logger import TaskLogger
|
||||
from modules.steam_special_offers.base.special_offer_watcher_abc import SpecialOfferWatcherABC
|
||||
from modules.steam_special_offers.model.game_offer import GameOffer
|
||||
|
||||
|
||||
class SteamOfferWatcher(SpecialOfferWatcherABC):
|
||||
def __init__(self, logger: TaskLogger):
|
||||
SpecialOfferWatcherABC.__init__(self)
|
||||
|
||||
self._logger = logger
|
||||
|
||||
def start(self):
|
||||
self.watch.start()
|
||||
|
||||
def _get_max_count(self) -> int:
|
||||
count = 0
|
||||
result = requests.get(f"https://store.steampowered.com/search/results?specials=1")
|
||||
soup = bs4.BeautifulSoup(result.text, "lxml")
|
||||
element = soup.find_all("div", {"class": "search_results_count"})
|
||||
if len(element) < 1:
|
||||
return count
|
||||
|
||||
count = int(element[0].contents[0].split(" ")[0].replace(",", ""))
|
||||
|
||||
return count
|
||||
|
||||
def _get_games_from_page(self, start: int, count: int) -> List[GameOffer]:
|
||||
games = List(GameOffer)
|
||||
result = requests.get(
|
||||
f"https://store.steampowered.com/search/results?query&start={start}&count={count}&force_infinite=1&specials=1"
|
||||
)
|
||||
soup = bs4.BeautifulSoup(result.text, "lxml")
|
||||
elements = soup.find_all("a", {"class": "search_result_row"})
|
||||
if len(elements) < 1:
|
||||
return games
|
||||
|
||||
for element in elements:
|
||||
name_element = element.find("span", {"class": "title"})
|
||||
original_price_element = element.find("div", {"class": "discount_original_price"})
|
||||
discount_element = element.find("div", {"class": "discount_pct"})
|
||||
discount_price_element = element.find("div", {"class": "discount_final_price"})
|
||||
|
||||
if (
|
||||
name_element is None
|
||||
or len(name_element.contents) < 1
|
||||
or original_price_element is None
|
||||
or len(original_price_element.contents) < 1
|
||||
or discount_element is None
|
||||
or len(discount_element.contents) < 1
|
||||
or discount_price_element is None
|
||||
or len(discount_price_element.contents) < 1
|
||||
):
|
||||
continue
|
||||
|
||||
name = name_element.contents[0]
|
||||
original_price = float(
|
||||
original_price_element.contents[0].replace(" ", "").replace("€", "").replace(",", ".")
|
||||
)
|
||||
discount = int(discount_element.contents[0].replace("%", ""))
|
||||
discount_price = float(
|
||||
discount_price_element.contents[0].replace(" ", "").replace("€", "").replace(",", ".")
|
||||
)
|
||||
games.add(GameOffer(name, original_price, discount_price, discount))
|
||||
|
||||
return games
|
||||
|
||||
async def _watch(self):
|
||||
self._logger.warn(__name__, "Watching")
|
||||
new_offers = List(GameOffer)
|
||||
|
||||
max = self._get_max_count()
|
||||
for i in range(0, max + 100, 100):
|
||||
self._logger.debug(__name__, f"Get special offers from {i}")
|
||||
new_offers.extend(self._get_games_from_page(i, 100))
|
||||
|
||||
self._logger.trace(__name__, "Finished watching")
|
||||
|
||||
@tasks.loop(seconds=5)
|
||||
async def watch(self):
|
||||
try:
|
||||
await self._watch()
|
||||
except Exception as e:
|
||||
self._logger.error(__name__, f"Steam offer watcher failed", e)
|
Loading…
Reference in New Issue
Block a user