Once and for all reformat.

Will be using black code formatter
This commit is contained in:
Emily Doherty 2024-12-08 17:07:26 -08:00
parent 3a06464c29
commit 34badf17eb
25 changed files with 359 additions and 295 deletions

View File

@ -3,6 +3,7 @@ aiohttp==3.9.5
aiosignal==1.3.1 aiosignal==1.3.1
anyio==4.4.0 anyio==4.4.0
attrs==23.2.0 attrs==23.2.0
black==24.10.0
certifi==2024.7.4 certifi==2024.7.4
croniter==2.0.5 croniter==2.0.5
discord-py-interactions==5.12.1 discord-py-interactions==5.12.1

View File

@ -11,7 +11,9 @@ from token_bot.persistant_database import database as pdb
class AlertsController: class AlertsController:
def __init__(self, session: aiohttp.ClientSession): def __init__(self, session: aiohttp.ClientSession):
self._pdb: pdb.Database = pdb.Database(session) self._pdb: pdb.Database = pdb.Database(session)
self.table = aiodynamo.client.Table = self._pdb.client.table(os.getenv('ALERTS_TABLE')) self.table = aiodynamo.client.Table = self._pdb.client.table(
os.getenv("ALERTS_TABLE")
)
@staticmethod @staticmethod
def _user_to_obj(user: int | User) -> User: def _user_to_obj(user: int | User) -> User:
@ -39,5 +41,3 @@ class AlertsController:
alert = self._alert_to_obj(alert) alert = self._alert_to_obj(alert)
user = self._user_to_obj(user) user = self._user_to_obj(user)
await alert.remove_user(self.table, user) await alert.remove_user(self.table, user)

View File

@ -11,8 +11,9 @@ from token_bot.persistant_database.user_schema import User
class UsersController: class UsersController:
def __init__(self, session: aiohttp.ClientSession): def __init__(self, session: aiohttp.ClientSession):
self._pdb: pdb.Database = pdb.Database(session) self._pdb: pdb.Database = pdb.Database(session)
self.table: aiodynamo.client.Table = self._pdb.client.table(os.getenv('USERS_TABLE')) self.table: aiodynamo.client.Table = self._pdb.client.table(
os.getenv("USERS_TABLE")
)
@staticmethod @staticmethod
def _user_to_obj(user: int | User) -> User: def _user_to_obj(user: int | User) -> User:
@ -67,4 +68,3 @@ class UsersController:
await user.get(self.table) await user.get(self.table)
user.subscribed_alerts.remove(alert) user.subscribed_alerts.remove(alert)
await user.put(self.table) await user.put(self.table)

View File

@ -18,8 +18,8 @@ class Core(Extension):
@listen(Startup) @listen(Startup)
async def on_start(self): async def on_start(self):
self.bot.logger.log(logging.INFO,"TokenBot Core ready") self.bot.logger.log(logging.INFO, "TokenBot Core ready")
self.bot.logger.log(logging.INFO,f"This is bot version {VERSION}") self.bot.logger.log(logging.INFO, f"This is bot version {VERSION}")
self._tdb = tdb.Database(aiohttp.ClientSession()) self._tdb = tdb.Database(aiohttp.ClientSession())
@slash_command() @slash_command()

View File

@ -9,14 +9,16 @@ from token_bot.token_database.region import Region
class History: class History:
def __init__(self, flavor: Flavor, region: Region): def __init__(self, flavor: Flavor, region: Region):
self._flavor : Flavor = flavor self._flavor: Flavor = flavor
self._region : Region = region self._region: Region = region
self._history : List[Tuple[datetime, int]] = [] self._history: List[Tuple[datetime, int]] = []
self._last_price_movement : int = 0 self._last_price_movement: int = 0
self._latest_price_datum : Tuple[datetime.datetime, int] | None = None self._latest_price_datum: Tuple[datetime.datetime, int] | None = None
self._update_triggers : List[UpdateTrigger] = [] self._update_triggers: List[UpdateTrigger] = []
for alert_type in AlertType: for alert_type in AlertType:
self._update_triggers.append(UpdateTrigger(Alert(alert_type, flavor, self._region))) self._update_triggers.append(
UpdateTrigger(Alert(alert_type, flavor, self._region))
)
@property @property
def flavor(self) -> Flavor: def flavor(self) -> Flavor:
@ -58,5 +60,3 @@ class History:
if trigger.alert == alert: if trigger.alert == alert:
return trigger return trigger
raise ValueError raise ValueError

View File

@ -9,23 +9,31 @@ from token_bot.token_database.region import Region
class HistoryManager: class HistoryManager:
HIGH_FIDELITY_PERIOD = '72h' HIGH_FIDELITY_PERIOD = "72h"
def __init__(self, token_db: tdb.Database): def __init__(self, token_db: tdb.Database):
self._history : Dict[Flavor, Dict[Region, History]] = {} self._history: Dict[Flavor, Dict[Region, History]] = {}
self._tdb : tdb.Database = token_db self._tdb: tdb.Database = token_db
for flavor in Flavor: for flavor in Flavor:
self._history[flavor] = {} self._history[flavor] = {}
for region in Region: for region in Region:
self._history[flavor][Region(region)] = History(flavor, Region(region)) self._history[flavor][Region(region)] = History(flavor, Region(region))
async def _retrieve_data(
async def _retrieve_data(self, flavor: Flavor, region: Region) -> List[Tuple[datetime.datetime, int]]: self, flavor: Flavor, region: Region
high_fidelity_time = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(hours=72) ) -> List[Tuple[datetime.datetime, int]]:
high_fidelity_time = datetime.datetime.now(
tz=datetime.UTC
) - datetime.timedelta(hours=72)
all_history = await self._tdb.history(flavor, region) all_history = await self._tdb.history(flavor, region)
high_fidelity_history = await self._tdb.history(flavor, region, self.HIGH_FIDELITY_PERIOD) high_fidelity_history = await self._tdb.history(
flavor, region, self.HIGH_FIDELITY_PERIOD
)
final_response = [] final_response = []
def _convert_to_datetime(data: Tuple[str, int]) -> Tuple[datetime.datetime, int]: def _convert_to_datetime(
data: Tuple[str, int]
) -> Tuple[datetime.datetime, int]:
return datetime.datetime.fromisoformat(data[0]), data[1] return datetime.datetime.fromisoformat(data[0]), data[1]
for data_point in all_history: for data_point in all_history:
@ -33,11 +41,12 @@ class HistoryManager:
if datetime_tuple[0] < high_fidelity_time: if datetime_tuple[0] < high_fidelity_time:
final_response.append(datetime_tuple) final_response.append(datetime_tuple)
final_response.extend(_convert_to_datetime(data_point) for data_point in high_fidelity_history) final_response.extend(
_convert_to_datetime(data_point) for data_point in high_fidelity_history
)
return final_response return final_response
async def load_data(self): async def load_data(self):
for flavor in Flavor: for flavor in Flavor:
for r in Region: for r in Region:
@ -48,19 +57,17 @@ class HistoryManager:
await history.add_price(item) await history.add_price(item)
self._history[flavor][region] = history self._history[flavor][region] = history
async def update_data(self, flavor: Flavor, region: Region) -> List[Alert]: async def update_data(self, flavor: Flavor, region: Region) -> List[Alert]:
history = self._history[flavor][region] history = self._history[flavor][region]
current_price_data = await self._tdb.current(flavor) current_price_data = await self._tdb.current(flavor)
current_region_data = current_price_data[region.value.lower()] current_region_data = current_price_data[region.value.lower()]
datum = ( datum = (
datetime.datetime.fromisoformat(current_region_data[0]), datetime.datetime.fromisoformat(current_region_data[0]),
current_region_data[1] current_region_data[1],
) )
if datum != history.last_price_datum: if datum != history.last_price_datum:
return await history.add_price(datum) return await history.add_price(datum)
return [] return []
def get_history(self, flavor, region) -> History: def get_history(self, flavor, region) -> History:
return self._history[flavor][region] return self._history[flavor][region]

View File

@ -8,10 +8,10 @@ from token_bot.token_database.flavor import Flavor
class UpdateTrigger: class UpdateTrigger:
def __init__(self, alert: Alert): def __init__(self, alert: Alert):
self._alert : Alert = alert self._alert: Alert = alert
self._last_trigger : Tuple[datetime.datetime, int] | None = None self._last_trigger: Tuple[datetime.datetime, int] | None = None
self._last_alerting: Tuple[datetime.datetime, int] | None = None self._last_alerting: Tuple[datetime.datetime, int] | None = None
self._squelched : bool = False self._squelched: bool = False
@property @property
def alert(self) -> Alert: def alert(self) -> Alert:
@ -29,20 +29,35 @@ class UpdateTrigger:
def squelched(self): def squelched(self):
return self._squelched return self._squelched
def _find_next_trigger(self, comparison_operator: Callable, starting_point: datetime.datetime, history: List[Tuple[datetime.datetime, int]]): def _find_next_trigger(
candidate_datum : Tuple[datetime.datetime, int] | None = None self,
comparison_operator: Callable,
starting_point: datetime.datetime,
history: List[Tuple[datetime.datetime, int]],
):
candidate_datum: Tuple[datetime.datetime, int] | None = None
for datum in history: for datum in history:
if datum[0] > starting_point and datum != history[-1]: if datum[0] > starting_point and datum != history[-1]:
if candidate_datum is None or comparison_operator(datum[1], candidate_datum[1]): if candidate_datum is None or comparison_operator(
datum[1], candidate_datum[1]
):
candidate_datum = datum candidate_datum = datum
self._last_trigger = candidate_datum self._last_trigger = candidate_datum
def check_and_update(self, new_datum: Tuple[datetime.datetime, int], history: List[Tuple[datetime.datetime, int]]) -> bool: def check_and_update(
self,
new_datum: Tuple[datetime.datetime, int],
history: List[Tuple[datetime.datetime, int]],
) -> bool:
match self.alert.flavor: match self.alert.flavor:
case Flavor.RETAIL: case Flavor.RETAIL:
start_time = datetime.datetime.fromisoformat('2020-11-15 00:00:01.000000000+00:00') start_time = datetime.datetime.fromisoformat(
"2020-11-15 00:00:01.000000000+00:00"
)
case Flavor.CLASSIC: case Flavor.CLASSIC:
start_time = datetime.datetime.fromisoformat('2023-05-23 00:00:01.000000000+00:00') start_time = datetime.datetime.fromisoformat(
"2023-05-23 00:00:01.000000000+00:00"
)
case _: case _:
raise NotImplementedError raise NotImplementedError

View File

@ -1,4 +1,3 @@
from .alert_type import AlertType from .alert_type import AlertType
from .user_schema import User from .user_schema import User
from .alert_schema import Alert from .alert_schema import Alert

View File

@ -7,7 +7,9 @@ class AlertCategory(Enum):
CUSTOM = 3 CUSTOM = 3
@staticmethod @staticmethod
def from_str(category: str): # It gets mad when I use the Type[AlertCategory] as a type hint def from_str(
category: str,
): # It gets mad when I use the Type[AlertCategory] as a type hint
match category: match category:
case "high_alert_button": case "high_alert_button":
return AlertCategory.HIGH return AlertCategory.HIGH
@ -16,4 +18,4 @@ class AlertCategory(Enum):
case "sp_add_button": case "sp_add_button":
return AlertCategory.CUSTOM return AlertCategory.CUSTOM
case _: case _:
return AlertCategory[category.upper()] return AlertCategory[category.upper()]

View File

@ -11,7 +11,9 @@ import token_bot.persistant_database as pdb
class Alert: class Alert:
def __init__(self, alert: pdb.AlertType, flavor: Flavor, region: Region, price: int = 0) -> None: def __init__(
self, alert: pdb.AlertType, flavor: Flavor, region: Region, price: int = 0
) -> None:
# AlertType is the Primary Key # AlertType is the Primary Key
self.alert_type: pdb.AlertType = alert self.alert_type: pdb.AlertType = alert
# Flavor (Retail, Classic) is the Sort Key # Flavor (Retail, Classic) is the Sort Key
@ -22,18 +24,18 @@ class Alert:
self.users: List[pdb.User] = [] self.users: List[pdb.User] = []
@classmethod @classmethod
def from_item(cls, primary_key: int, sort_key: str, users: List[int]) -> 'Alert': def from_item(cls, primary_key: int, sort_key: str, users: List[int]) -> "Alert":
alert_type = pdb.AlertType(primary_key) alert_type = pdb.AlertType(primary_key)
flavor_repr, region_repr, price_repr = sort_key.split('-') flavor_repr, region_repr, price_repr = sort_key.split("-")
flavor = Flavor(int(flavor_repr)) flavor = Flavor(int(flavor_repr))
region = Region(region_repr) region = Region(region_repr)
price = int(price_repr) price = int(price_repr)
return cls(alert_type, flavor, region, price) return cls(alert_type, flavor, region, price)
@classmethod @classmethod
def from_str(cls, string_trinity: str) -> 'Alert': def from_str(cls, string_trinity: str) -> "Alert":
alert_repr, flavor_repr, region_repr, price_repr = string_trinity.split('-') alert_repr, flavor_repr, region_repr, price_repr = string_trinity.split("-")
if len(string_trinity.split('-')) != 4: if len(string_trinity.split("-")) != 4:
raise ValueError raise ValueError
alert = pdb.AlertType(int(alert_repr)) alert = pdb.AlertType(int(alert_repr))
flavor = Flavor(int(flavor_repr)) flavor = Flavor(int(flavor_repr))
@ -61,20 +63,24 @@ class Alert:
def key(self) -> dict[str, str | int]: def key(self) -> dict[str, str | int]:
return { return {
self.primary_key_name: self.primary_key, self.primary_key_name: self.primary_key,
self.sort_key_name: self.sort_key self.sort_key_name: self.sort_key,
} }
def __str__(self): def __str__(self):
return f"{self.alert_type.value}-{self.flavor.value}-{self.region.value}-{self.price}" return f"{self.alert_type.value}-{self.flavor.value}-{self.region.value}-{self.price}"
def __eq__(self, other): def __eq__(self, other):
return self.alert_type == other.alert_type and self.flavor == other.flavor and self.price == other.price return (
self.alert_type == other.alert_type
and self.flavor == other.flavor
and self.price == other.price
)
def to_human_string(self): def to_human_string(self):
if self.alert_type == AlertType.SPECIFIC_PRICE: if self.alert_type == AlertType.SPECIFIC_PRICE:
raise NotImplementedError raise NotImplementedError
else: else:
alert_type_str = ' '.join(self.alert_type.name.split("_")) alert_type_str = " ".join(self.alert_type.name.split("_"))
return f"{alert_type_str.title()}" return f"{alert_type_str.title()}"
async def _lazy_load(self, table: Table, consistent: bool = False) -> None: async def _lazy_load(self, table: Table, consistent: bool = False) -> None:
@ -95,19 +101,16 @@ class Alert:
item={ item={
self.primary_key_name: self.primary_key, self.primary_key_name: self.primary_key,
self.sort_key_name: self.sort_key, self.sort_key_name: self.sort_key,
'users': user_ids "users": user_ids,
} }
) )
async def get(self, table: Table, consistent: bool = False) -> bool: async def get(self, table: Table, consistent: bool = False) -> bool:
try: try:
response = await table.get_item( response = await table.get_item(key=self.key, consistent_read=consistent)
key=self.key,
consistent_read=consistent
)
except ItemNotFound: except ItemNotFound:
return False return False
self.users = [pdb.User(int(user_id)) for user_id in response['users']] self.users = [pdb.User(int(user_id)) for user_id in response["users"]]
self._loaded = True self._loaded = True
return True return True
@ -116,13 +119,17 @@ class Alert:
return self.users return self.users
async def add_user(self, table: Table, user: pdb.User, consistent: bool = False) -> None: async def add_user(
self, table: Table, user: pdb.User, consistent: bool = False
) -> None:
await self._lazy_load(table, consistent=consistent) await self._lazy_load(table, consistent=consistent)
if user not in self.users: if user not in self.users:
await self._append_user(table=table, user=user) await self._append_user(table=table, user=user)
async def remove_user(self, table: Table, user: pdb.User, consistent: bool = True) -> None: async def remove_user(
self, table: Table, user: pdb.User, consistent: bool = True
) -> None:
await self._lazy_load(table, consistent=consistent) await self._lazy_load(table, consistent=consistent)
if user in self.users: if user in self.users:

View File

@ -1,5 +1,6 @@
from enum import Enum from enum import Enum
class AlertType(Enum): class AlertType(Enum):
ALL_TIME_HIGH = 1 ALL_TIME_HIGH = 1
ALL_TIME_LOW = 2 ALL_TIME_LOW = 2
@ -37,4 +38,4 @@ class AlertType(Enum):
case "All Time Low": case "All Time Low":
return AlertType.ALL_TIME_LOW return AlertType.ALL_TIME_LOW
case _: case _:
return AlertType.SPECIFIC_PRICE return AlertType.SPECIFIC_PRICE

View File

@ -8,5 +8,6 @@ from aiodynamo.http.aiohttp import AIOHTTP
class Database: class Database:
def __init__(self, session: aiohttp.ClientSession): def __init__(self, session: aiohttp.ClientSession):
self.client = Client(AIOHTTP(session), Credentials.auto(), os.getenv('AWS_REGION')) self.client = Client(
AIOHTTP(session), Credentials.auto(), os.getenv("AWS_REGION")
)

View File

@ -8,7 +8,12 @@ from token_bot.token_database.region import Region
class User: class User:
def __init__(self, user_id: int, region: Region = None, subscribed_alerts: List['pdb.Alert'] = None) -> None: def __init__(
self,
user_id: int,
region: Region = None,
subscribed_alerts: List["pdb.Alert"] = None,
) -> None:
self.user_id: int = user_id self.user_id: int = user_id
self._loaded: bool = False self._loaded: bool = False
self.region: Region = region self.region: Region = region
@ -21,7 +26,9 @@ class User:
return hash(self.user_id) return hash(self.user_id)
@classmethod @classmethod
def from_item(cls, primary_key: int, region: Region, subscribed_alerts: List[str]) -> 'User': def from_item(
cls, primary_key: int, region: Region, subscribed_alerts: List[str]
) -> "User":
alerts = [pdb.Alert.from_str(alert_str) for alert_str in subscribed_alerts] alerts = [pdb.Alert.from_str(alert_str) for alert_str in subscribed_alerts]
return cls(primary_key, region, alerts) return cls(primary_key, region, alerts)
@ -31,17 +38,18 @@ class User:
@property @property
def primary_key_name(self) -> str: def primary_key_name(self) -> str:
return 'user_id' return "user_id"
@property @property
def key(self) -> Dict[str, str]: def key(self) -> Dict[str, str]:
return { return {self.primary_key_name: self.primary_key}
self.primary_key_name: self.primary_key
}
def _subscribed_alerts_as_trinity_list(self) -> List[str]: def _subscribed_alerts_as_trinity_list(self) -> List[str]:
return [str(alert) for alert in self.subscribed_alerts] if self.subscribed_alerts else [] return (
[str(alert) for alert in self.subscribed_alerts]
if self.subscribed_alerts
else []
)
async def _lazy_load(self, table: Table, consistent: bool = False) -> None: async def _lazy_load(self, table: Table, consistent: bool = False) -> None:
if consistent or not self._loaded: if consistent or not self._loaded:
@ -51,8 +59,8 @@ class User:
await table.put_item( await table.put_item(
item={ item={
self.primary_key_name: self.primary_key, self.primary_key_name: self.primary_key,
'region': self.region, "region": self.region,
'subscribed_alerts': self._subscribed_alerts_as_trinity_list() "subscribed_alerts": self._subscribed_alerts_as_trinity_list(),
} }
) )
@ -65,26 +73,27 @@ class User:
async def get(self, table: Table, consistent: bool = False) -> bool: async def get(self, table: Table, consistent: bool = False) -> bool:
try: try:
response = await table.get_item( response = await table.get_item(key=self.key, consistent_read=consistent)
key=self.key,
consistent_read=consistent
)
except ItemNotFound: except ItemNotFound:
return False return False
self.subscribed_alerts = [] self.subscribed_alerts = []
for string_trinity in response['subscribed_alerts']: for string_trinity in response["subscribed_alerts"]:
self.subscribed_alerts.append(pdb.Alert.from_str(string_trinity)) self.subscribed_alerts.append(pdb.Alert.from_str(string_trinity))
self.region = Region(response['region']) self.region = Region(response["region"])
return True return True
async def add_alert(self, table: Table, alert: 'pdb.Alert', consistent: bool = False) -> None: async def add_alert(
self, table: Table, alert: "pdb.Alert", consistent: bool = False
) -> None:
await self._lazy_load(table, consistent=consistent) await self._lazy_load(table, consistent=consistent)
if alert not in self.subscribed_alerts: if alert not in self.subscribed_alerts:
self.subscribed_alerts.append(alert) self.subscribed_alerts.append(alert)
await self.put(table) await self.put(table)
async def remove_alert(self, table: Table, alert: 'pdb.Alert', consistent: bool = True) -> None: async def remove_alert(
self, table: Table, alert: "pdb.Alert", consistent: bool = True
) -> None:
await self._lazy_load(table, consistent=consistent) await self._lazy_load(table, consistent=consistent)
if alert in self.subscribed_alerts: if alert in self.subscribed_alerts:
self.subscribed_alerts.remove(alert) self.subscribed_alerts.remove(alert)

View File

@ -10,16 +10,12 @@ class TokenBot:
load_dotenv() load_dotenv()
print("#### WoW Token Bot Startup ####") print("#### WoW Token Bot Startup ####")
logging.basicConfig( logging.basicConfig(
format='%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s', format="%(asctime)s.%(msecs)03d %(levelname)s %(module)s - %(funcName)s: %(message)s",
datefmt='%Y-%m-%d %H:%M:%S', datefmt="%Y-%m-%d %H:%M:%S",
) )
log = logging.getLogger("TokenBotLogger") log = logging.getLogger("TokenBotLogger")
log.setLevel(logging.INFO) log.setLevel(logging.INFO)
self.bot = Client( self.bot = Client(intents=Intents.DEFAULT, asyncio_debug=True, logger=log)
intents=Intents.DEFAULT,
asyncio_debug=True,
logger=log
)
def run(self): def run(self):
self.bot.load_extension("token_bot.core") self.bot.load_extension("token_bot.core")

View File

@ -34,7 +34,9 @@ class Database:
raise TokenHttpException(resp.status) raise TokenHttpException(resp.status)
async def current(self, flavor: Flavor) -> dict: async def current(self, flavor: Flavor) -> dict:
return await self._get_data(f'current/{flavor.name.lower()}.json') return await self._get_data(f"current/{flavor.name.lower()}.json")
async def history(self, flavor: Flavor, region: Region, relative_time: str = 'all'): async def history(self, flavor: Flavor, region: Region, relative_time: str = "all"):
return await self._get_data(f'relative/{flavor.name.lower()}/{region.value.lower()}/{relative_time}.json') return await self._get_data(
f"relative/{flavor.name.lower()}/{region.value.lower()}/{relative_time}.json"
)

View File

@ -4,4 +4,3 @@ from enum import Enum
class Flavor(Enum): class Flavor(Enum):
RETAIL = 1 RETAIL = 1
CLASSIC = 2 CLASSIC = 2

View File

@ -1,7 +1,8 @@
from enum import Enum from enum import Enum
class Region(str, Enum): class Region(str, Enum):
US = 'us' US = "us"
EU = 'eu' EU = "eu"
KR = 'kr' KR = "kr"
TW = 'tw' TW = "tw"

View File

@ -5,8 +5,19 @@ import logging
from typing import Type, Dict, List from typing import Type, Dict, List
import aiohttp import aiohttp
from interactions import Extension, SlashContext, component_callback, \ from interactions import (
ComponentContext, StringSelectMenu, Message, Embed, EmbedField, is_owner, check, StringSelectOption Extension,
SlashContext,
component_callback,
ComponentContext,
StringSelectMenu,
Message,
Embed,
EmbedField,
is_owner,
check,
StringSelectOption,
)
from interactions import Task, IntervalTrigger from interactions import Task, IntervalTrigger
from interactions import slash_command, listen from interactions import slash_command, listen
from interactions.api.events import Component from interactions.api.events import Component
@ -29,6 +40,7 @@ from token_bot.ui.select_menus.region_menu import REGION_MENU
#### Static Helper Functions #### Static Helper Functions
async def gather_alerts_by_flavor(alerts: List[Alert]) -> Dict[Flavor, List[Alert]]: async def gather_alerts_by_flavor(alerts: List[Alert]) -> Dict[Flavor, List[Alert]]:
alerts_by_flavor = {} alerts_by_flavor = {}
for alert in alerts: for alert in alerts:
@ -39,7 +51,6 @@ async def gather_alerts_by_flavor(alerts: List[Alert]) -> Dict[Flavor, List[Aler
return alerts_by_flavor return alerts_by_flavor
class Tracker(Extension): class Tracker(Extension):
def __init__(self, bot): def __init__(self, bot):
self._users: UsersController | None = None self._users: UsersController | None = None
@ -47,7 +58,6 @@ class Tracker(Extension):
self._tdb: tdb.Database | None = None self._tdb: tdb.Database | None = None
self._history_manager: HistoryManager | None = None self._history_manager: HistoryManager | None = None
################################### ###################################
# Task Functions # # Task Functions #
################################### ###################################
@ -68,21 +78,24 @@ class Tracker(Extension):
users_alerts[user].append(alert) users_alerts[user].append(alert)
for user in users_alerts: for user in users_alerts:
discord_user = await self.bot.fetch_user(user.user_id) discord_user = await self.bot.fetch_user(user.user_id)
embeds = [Embed( embeds = [
title="TokenBot Tracker Alert Triggered", Embed(
color=0xb10000, title="TokenBot Tracker Alert Triggered",
description=f"Hello, you requested to be sent an alert when the price of the World of Warcraft " color=0xB10000,
f"token reaches a certain value.\n\n" description=f"Hello, you requested to be sent an alert when the price of the World of Warcraft "
f"As a reminder, you can remove an alert via ```/remove-alert```\n" f"token reaches a certain value.\n\n"
f"or you can remove all registrations via ```/remove-registration```\n\n" f"As a reminder, you can remove an alert via ```/remove-alert```\n"
)] f"or you can remove all registrations via ```/remove-registration```\n\n",
)
]
alerts_by_flavor = await gather_alerts_by_flavor(users_alerts[user]) alerts_by_flavor = await gather_alerts_by_flavor(users_alerts[user])
for flavor in alerts_by_flavor: for flavor in alerts_by_flavor:
embeds.append(await self.render_alert_flavor(alerts_by_flavor[flavor], user=user)) embeds.append(
await self.render_alert_flavor(alerts_by_flavor[flavor], user=user)
)
await discord_user.send(embeds=embeds) await discord_user.send(embeds=embeds)
################################### ###################################
# Slash Commands # # Slash Commands #
################################### ###################################
@ -97,28 +110,30 @@ class Tracker(Extension):
self.bot.logger.log(logging.INFO, "TokenBot Tracker: Initialized") self.bot.logger.log(logging.INFO, "TokenBot Tracker: Initialized")
self.bot.logger.log(logging.INFO, "TokenBot Tracker: Loading Historical Data") self.bot.logger.log(logging.INFO, "TokenBot Tracker: Loading Historical Data")
await self._history_manager.load_data() await self._history_manager.load_data()
self.bot.logger.log(logging.INFO, "TokenBot Tracker: Loading Historical Data Finished") self.bot.logger.log(
logging.INFO, "TokenBot Tracker: Loading Historical Data Finished"
)
self.bot.logger.log(logging.INFO, "TokenBot Tracker: Started") self.bot.logger.log(logging.INFO, "TokenBot Tracker: Started")
self.update_data.start() self.update_data.start()
@slash_command( @slash_command(
name="register", name="register",
description="Register with TokenBot for alerts on token price changes." description="Register with TokenBot for alerts on token price changes.",
) )
async def register(self, ctx: SlashContext): async def register(self, ctx: SlashContext):
text = ("## Select a region to register with \n\n" text = (
"Please note: \n" "## Select a region to register with \n\n"
"* You can only be registered with one region at a time \n" "Please note: \n"
"* Changing your region will remove all previous alerts you have signed up for \n" "* You can only be registered with one region at a time \n"
"* You can remove all alerts and registration using ```/remove-registration```") "* Changing your region will remove all previous alerts you have signed up for \n"
"* You can remove all alerts and registration using ```/remove-registration```"
)
menu = copy.deepcopy(REGION_MENU) menu = copy.deepcopy(REGION_MENU)
await ctx.send(text, components=menu, ephemeral=True) await ctx.send(text, components=menu, ephemeral=True)
@slash_command( @slash_command(
name="remove-registration", name="remove-registration",
description="Remove all alerts and registration from TokenBot" description="Remove all alerts and registration from TokenBot",
) )
async def remove_registration(self, ctx: SlashContext): async def remove_registration(self, ctx: SlashContext):
if await self._users.exists(ctx.user.id): if await self._users.exists(ctx.user.id):
@ -127,43 +142,35 @@ class Tracker(Extension):
await self._alerts.remove_user(alert, user) await self._alerts.remove_user(alert, user)
await self._users.delete(ctx.user.id) await self._users.delete(ctx.user.id)
await ctx.send("All alert subscriptions and user registration deleted", ephemeral=True) await ctx.send(
"All alert subscriptions and user registration deleted", ephemeral=True
)
@slash_command( @slash_command(
name="exists", name="exists", description="Check if you are registered with TokenBot"
description="Check if you are registered with TokenBot"
) )
@check(is_owner()) @check(is_owner())
async def exists(self, ctx: SlashContext): async def exists(self, ctx: SlashContext):
await ctx.send(str(await self._users.exists(ctx.user.id)), ephemeral=True) await ctx.send(str(await self._users.exists(ctx.user.id)), ephemeral=True)
@slash_command(description="The current retail token cost")
@slash_command(
description="The current retail token cost"
)
async def current(self, ctx: SlashContext): async def current(self, ctx: SlashContext):
current_str = await self.get_current_token(ctx, tdb.Flavor.RETAIL) current_str = await self.get_current_token(ctx, tdb.Flavor.RETAIL)
await ctx.send(current_str, ephemeral=True) await ctx.send(current_str, ephemeral=True)
@slash_command(description="The current classic token cost")
@slash_command(
description="The current classic token cost"
)
async def current_classic(self, ctx: SlashContext): async def current_classic(self, ctx: SlashContext):
current_str = await self.get_current_token(ctx, tdb.Flavor.CLASSIC) current_str = await self.get_current_token(ctx, tdb.Flavor.CLASSIC)
await ctx.send(current_str, ephemeral=True) await ctx.send(current_str, ephemeral=True)
@slash_command(name="add-alert", description="Add an alert listener")
@slash_command(
name="add-alert",
description="Add an alert listener"
)
async def add_alert(self, ctx: SlashContext): async def add_alert(self, ctx: SlashContext):
if not await self._users.exists(ctx.user.id): if not await self._users.exists(ctx.user.id):
await ctx.send("You are not registered with any region\n" await ctx.send(
"Please register with /register before adding alerts", "You are not registered with any region\n"
ephemeral=True) "Please register with /register before adding alerts",
ephemeral=True,
)
return return
user = await self._users.get(ctx.user.id) user = await self._users.get(ctx.user.id)
@ -186,23 +193,25 @@ class Tracker(Extension):
if not await self._users.is_subscribed(user, alert): if not await self._users.is_subscribed(user, alert):
await asyncio.gather( await asyncio.gather(
self._users.add_alert(user, alert), self._users.add_alert(user, alert),
self._alerts.add_user(alert, user) self._alerts.add_user(alert, user),
) )
await ctx.send("Successfully added alert", ephemeral=True) await ctx.send("Successfully added alert", ephemeral=True)
else: else:
await ctx.send("You are already subscribed to this alert", ephemeral=True) await ctx.send(
"You are already subscribed to this alert", ephemeral=True
)
@slash_command( @slash_command(
name="remove-alert", name="remove-alert", description="Remove an alert you have signed up for"
description="Remove an alert you have signed up for"
) )
async def remove_alert(self, ctx: SlashContext): async def remove_alert(self, ctx: SlashContext):
if not await self._users.exists(ctx.user.id): if not await self._users.exists(ctx.user.id):
await ctx.send("You are not registered with any region\n" await ctx.send(
"Please register with /register before adding alerts", "You are not registered with any region\n"
ephemeral=True) "Please register with /register before adding alerts",
ephemeral=True,
)
return return
user = await self._users.get(ctx.user.id) user = await self._users.get(ctx.user.id)
alerts = await self._users.list_alerts(user) alerts = await self._users.list_alerts(user)
@ -217,20 +226,20 @@ class Tracker(Extension):
else: else:
await asyncio.gather( await asyncio.gather(
self._users.remove_alert(user, alert), self._users.remove_alert(user, alert),
self._alerts.remove_user(alert, user) self._alerts.remove_user(alert, user),
) )
await ctx.send("Successfully removed alert", ephemeral=True) await ctx.send("Successfully removed alert", ephemeral=True)
@slash_command( @slash_command(
name="list-alerts", name="list-alerts", description="List all alerts you have signed up for"
description="List all alerts you have signed up for"
) )
async def list_alerts(self, ctx: SlashContext): async def list_alerts(self, ctx: SlashContext):
if not await self._users.exists(ctx.user.id): if not await self._users.exists(ctx.user.id):
await ctx.send("You are not registered with any region\n" await ctx.send(
"Please register with /register before adding alerts", "You are not registered with any region\n"
ephemeral=True) "Please register with /register before adding alerts",
ephemeral=True,
)
return return
user = await self._users.get(ctx.user.id) user = await self._users.get(ctx.user.id)
alerts = await self._users.list_alerts(user) alerts = await self._users.list_alerts(user)
@ -238,55 +247,66 @@ class Tracker(Extension):
await ctx.send("You do not have any alerts registered", ephemeral=True) await ctx.send("You do not have any alerts registered", ephemeral=True)
return return
alerts_str = f"You have {len(alerts)} out of 25 maximum alerts registered" alerts_str = f"You have {len(alerts)} out of 25 maximum alerts registered"
embeds = [Embed( embeds = [
title="List of TokenBot Tracker Alerts", Embed(
color=0x0000b1, title="List of TokenBot Tracker Alerts",
description=alerts_str color=0x0000B1,
)] description=alerts_str,
)
]
alerts_by_flavor = await gather_alerts_by_flavor(alerts) alerts_by_flavor = await gather_alerts_by_flavor(alerts)
for flavor in alerts_by_flavor: for flavor in alerts_by_flavor:
embeds.append(await self.render_alert_flavor(alerts_by_flavor[flavor], user=user)) embeds.append(
await self.render_alert_flavor(alerts_by_flavor[flavor], user=user)
)
await ctx.send(embeds=embeds, ephemeral=True) await ctx.send(embeds=embeds, ephemeral=True)
################################### ###################################
# Callbacks Commands # # Callbacks Commands #
################################### ###################################
@component_callback('flavor_menu') @component_callback("flavor_menu")
async def flavor_menu(self, ctx: ComponentContext): async def flavor_menu(self, ctx: ComponentContext):
await ctx.send(f"Selected Flavor: {ctx.values[0]}", ephemeral=True) await ctx.send(f"Selected Flavor: {ctx.values[0]}", ephemeral=True)
@component_callback('high_alert_menu') @component_callback("high_alert_menu")
async def alert_menu(self, ctx: ComponentContext): async def alert_menu(self, ctx: ComponentContext):
await ctx.send(f"Selected Alert: {ctx.values[0]}", ephemeral=True) await ctx.send(f"Selected Alert: {ctx.values[0]}", ephemeral=True)
@component_callback('low_alert_menu') @component_callback("low_alert_menu")
async def alert_menu(self, ctx: ComponentContext): async def alert_menu(self, ctx: ComponentContext):
await ctx.send(f"Selected Alert: {ctx.values[0]}", ephemeral=True) await ctx.send(f"Selected Alert: {ctx.values[0]}", ephemeral=True)
@component_callback('remove_alert_menu') @component_callback("remove_alert_menu")
async def remove_alert_menu(self, ctx: ComponentContext): async def remove_alert_menu(self, ctx: ComponentContext):
await ctx.send(f"You have selected to remove the following alert: {ctx.values[0].title()}", ephemeral=True) await ctx.send(
f"You have selected to remove the following alert: {ctx.values[0].title()}",
ephemeral=True,
)
@component_callback('region_menu') @component_callback("region_menu")
async def region_menu(self, ctx: ComponentContext): async def region_menu(self, ctx: ComponentContext):
user = User(ctx.user.id, Region(ctx.values[0].lower()), subscribed_alerts=[]) user = User(ctx.user.id, Region(ctx.values[0].lower()), subscribed_alerts=[])
await self._users.add(user) await self._users.add(user)
discord_user = await self.bot.fetch_user(user.user_id) discord_user = await self.bot.fetch_user(user.user_id)
await discord_user.send("You have successfully registered with TokenBot!\n" await discord_user.send(
"Most interactions will happen in direct messages with TokenBot here.\n" "You have successfully registered with TokenBot!\n"
"You can remove your registration and alerts at any time using ```/remove-registration```\n") "Most interactions will happen in direct messages with TokenBot here.\n"
await ctx.send(f"Successfully registered with the {ctx.values[0]} region", ephemeral=True) "You can remove your registration and alerts at any time using ```/remove-registration```\n"
)
await ctx.send(
f"Successfully registered with the {ctx.values[0]} region", ephemeral=True
)
@component_callback('high_alert_button') @component_callback("high_alert_button")
async def high_alert_button(self, ctx: ComponentContext): async def high_alert_button(self, ctx: ComponentContext):
await ctx.send("You selected to add a High Price Alert", ephemeral=True) await ctx.send("You selected to add a High Price Alert", ephemeral=True)
@component_callback('low_alert_button') @component_callback("low_alert_button")
async def low_alert_button(self, ctx: ComponentContext): async def low_alert_button(self, ctx: ComponentContext):
await ctx.send("You selected to add a Low Price Alert", ephemeral=True) await ctx.send("You selected to add a Low Price Alert", ephemeral=True)
@component_callback('custom_alert_button') @component_callback("custom_alert_button")
async def custom_alert_button(self, ctx: ComponentContext): async def custom_alert_button(self, ctx: ComponentContext):
await ctx.send("You selected to add a Custom Price Alert", ephemeral=True) await ctx.send("You selected to add a Custom Price Alert", ephemeral=True)
@ -298,34 +318,40 @@ class Tracker(Extension):
user: User = await self._users.get(ctx.user.id) user: User = await self._users.get(ctx.user.id)
region = user.region.name region = user.region.name
region_history = self._history_manager.get_history(flavor, user.region) region_history = self._history_manager.get_history(flavor, user.region)
price_movement_str = format(region_history.last_price_movement, ',') price_movement_str = format(region_history.last_price_movement, ",")
if region_history.last_price_movement > 0: if region_history.last_price_movement > 0:
price_movement_str = f"+{price_movement_str}" price_movement_str = f"+{price_movement_str}"
return (f"Last Price Value for {region}: {format(region_history.last_price_datum[1], ",")}\n" return (
f"Last Update Time: {region_history.last_price_datum[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n" f"Last Price Value for {region}: {format(region_history.last_price_datum[1], ",")}\n"
f"Last Price Movement: {price_movement_str}") f"Last Update Time: {region_history.last_price_datum[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n"
f"Last Price Movement: {price_movement_str}"
)
async def remove_alert_select_menu(self, ctx: SlashContext, user: User): async def remove_alert_select_menu(self, ctx: SlashContext, user: User):
alerts_by_flavor = await gather_alerts_by_flavor(user.subscribed_alerts) alerts_by_flavor = await gather_alerts_by_flavor(user.subscribed_alerts)
select_options: List[StringSelectOption] = [] select_options: List[StringSelectOption] = []
for flavor in alerts_by_flavor: for flavor in alerts_by_flavor:
for alert in alerts_by_flavor[flavor]: for alert in alerts_by_flavor[flavor]:
select_options.append(StringSelectOption( select_options.append(
label=f"{alert.flavor.name.lower().title()} {alert.to_human_string()}", StringSelectOption(
value=f"{alert.flavor.name.lower()} {alert.to_human_string()}" label=f"{alert.flavor.name.lower().title()} {alert.to_human_string()}",
)) value=f"{alert.flavor.name.lower()} {alert.to_human_string()}",
)
)
menu = StringSelectMenu( menu = StringSelectMenu(
select_options, select_options,
placeholder="Select an alert to remove", placeholder="Select an alert to remove",
custom_id="remove_alert_menu" custom_id="remove_alert_menu",
)
message = await ctx.send(
"Select an alert to remove", components=menu, ephemeral=True
) )
message = await ctx.send("Select an alert to remove", components=menu, ephemeral=True)
try: try:
alert_component: Component = await self.bot.wait_for_component(messages=message, alert_component: Component = await self.bot.wait_for_component(
components=menu, timeout=30) messages=message, components=menu, timeout=30
)
except TimeoutError: except TimeoutError:
menu.disabled = True menu.disabled = True
await message.edit(context=ctx, components=menu, content="Timed out") await message.edit(context=ctx, components=menu, content="Timed out")
@ -335,23 +361,24 @@ class Tracker(Extension):
await message.edit(context=ctx, components=menu) await message.edit(context=ctx, components=menu)
selection_split = alert_component.ctx.values[0].split(" ") selection_split = alert_component.ctx.values[0].split(" ")
flavor = Flavor[selection_split[0].upper()] flavor = Flavor[selection_split[0].upper()]
alert_type = AlertType.from_str(' '.join(selection_split[1:])) alert_type = AlertType.from_str(" ".join(selection_split[1:]))
return Alert(alert_type, flavor, user.region) return Alert(alert_type, flavor, user.region)
async def flavor_select_menu(self, ctx: SlashContext) -> Type[Flavor]: async def flavor_select_menu(self, ctx: SlashContext) -> Type[Flavor]:
flavor_menu = copy.deepcopy(FLAVOR_MENU) flavor_menu = copy.deepcopy(FLAVOR_MENU)
flavor_message = await ctx.send( flavor_message = await ctx.send(
"Select a flavor to add alerts for", "Select a flavor to add alerts for", components=flavor_menu, ephemeral=True
components=flavor_menu, )
ephemeral=True)
try: try:
flavor_component: Component = await self.bot.wait_for_component(messages=flavor_message, flavor_component: Component = await self.bot.wait_for_component(
components=flavor_menu, timeout=30) messages=flavor_message, components=flavor_menu, timeout=30
)
except TimeoutError: except TimeoutError:
flavor_menu.disabled = True flavor_menu.disabled = True
await flavor_message.edit(context=ctx, components=flavor_menu, content="Timed out") await flavor_message.edit(
context=ctx, components=flavor_menu, content="Timed out"
)
raise TimeoutError raise TimeoutError
else: else:
flavor = Flavor[flavor_component.ctx.values[0].upper()] flavor = Flavor[flavor_component.ctx.values[0].upper()]
@ -359,21 +386,21 @@ class Tracker(Extension):
await flavor_message.edit(context=ctx, components=flavor_menu) await flavor_message.edit(context=ctx, components=flavor_menu)
return flavor return flavor
async def alert_category_select_menu(self, ctx: SlashContext) -> AlertCategory: async def alert_category_select_menu(self, ctx: SlashContext) -> AlertCategory:
alert_type_button = copy.deepcopy(ALERT_TYPE_ROW) alert_type_button = copy.deepcopy(ALERT_TYPE_ROW)
alert_type_message = await ctx.send( alert_type_message = await ctx.send(
"Select an alert type to add", "Select an alert type to add", components=alert_type_button, ephemeral=True
components=alert_type_button, )
ephemeral=True)
try: try:
alert_type_component: Component = await self.bot.wait_for_component(messages=alert_type_message, alert_type_component: Component = await self.bot.wait_for_component(
components=alert_type_button, messages=alert_type_message, components=alert_type_button, timeout=30
timeout=30) )
except TimeoutError: except TimeoutError:
for button in alert_type_button[0].components: for button in alert_type_button[0].components:
button.disabled = True button.disabled = True
await alert_type_message.edit(context=ctx, components=alert_type_button, content="Timed out") await alert_type_message.edit(
context=ctx, components=alert_type_button, content="Timed out"
)
raise TimeoutError raise TimeoutError
else: else:
alert_type = AlertCategory.from_str(alert_type_component.ctx.custom_id) alert_type = AlertCategory.from_str(alert_type_component.ctx.custom_id)
@ -382,10 +409,13 @@ class Tracker(Extension):
await alert_type_message.edit(context=ctx, components=alert_type_button) await alert_type_message.edit(context=ctx, components=alert_type_button)
return alert_type return alert_type
async def _alert_select_menu_handler(
async def _alert_select_menu_handler(self, ctx: SlashContext, menu: StringSelectMenu, message: Message) -> AlertType: self, ctx: SlashContext, menu: StringSelectMenu, message: Message
) -> AlertType:
try: try:
component: Component = await self.bot.wait_for_component(messages=message, components=menu, timeout=30) component: Component = await self.bot.wait_for_component(
messages=message, components=menu, timeout=30
)
except TimeoutError: except TimeoutError:
menu.disabled = True menu.disabled = True
await message.edit(context=ctx, components=menu, content="Timed out") await message.edit(context=ctx, components=menu, content="Timed out")
@ -395,26 +425,27 @@ class Tracker(Extension):
await message.edit(context=ctx, components=menu) await message.edit(context=ctx, components=menu)
return AlertType.from_str(component.ctx.values[0]) return AlertType.from_str(component.ctx.values[0])
async def high_alert_select_menu(self, ctx: SlashContext) -> AlertType: async def high_alert_select_menu(self, ctx: SlashContext) -> AlertType:
high_menu = copy.deepcopy(HIGH_ALERT_MENU) high_menu = copy.deepcopy(HIGH_ALERT_MENU)
high_message = await ctx.send( high_message = await ctx.send(
"Select a time range to add a High Alert for", "Select a time range to add a High Alert for",
components=high_menu, components=high_menu,
ephemeral=True) ephemeral=True,
)
return await self._alert_select_menu_handler(ctx, high_menu, high_message) return await self._alert_select_menu_handler(ctx, high_menu, high_message)
async def low_alert_select_menu(self, ctx: SlashContext) -> AlertType: async def low_alert_select_menu(self, ctx: SlashContext) -> AlertType:
low_menu = copy.deepcopy(LOW_ALERT_MENU) low_menu = copy.deepcopy(LOW_ALERT_MENU)
low_message = await ctx.send( low_message = await ctx.send(
"Select a time range to add a Low Alert for", "Select a time range to add a Low Alert for",
components=low_menu, components=low_menu,
ephemeral=True) ephemeral=True,
)
return await self._alert_select_menu_handler(ctx, low_menu, low_message) return await self._alert_select_menu_handler(ctx, low_menu, low_message)
async def render_alert_flavor(
async def render_alert_flavor(self, alerts: List[Alert], user: User | None = None) -> Embed: self, alerts: List[Alert], user: User | None = None
) -> Embed:
region = alerts[0].region region = alerts[0].region
flavor = alerts[0].flavor flavor = alerts[0].flavor
fields: List[EmbedField] = [] fields: List[EmbedField] = []
@ -422,28 +453,36 @@ class Tracker(Extension):
history = self._history_manager.get_history(alert.flavor, alert.region) history = self._history_manager.get_history(alert.flavor, alert.region)
trigger = await history.find_update_trigger_from_alert(alert) trigger = await history.find_update_trigger_from_alert(alert)
if trigger.last_trigger is not None: if trigger.last_trigger is not None:
alert_str = (f"Last Alerting Price Value: {format(trigger.last_alerting[1], ",")}\n" alert_str = (
f"Last Alerting Time: {trigger.last_alerting[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n") f"Last Alerting Price Value: {format(trigger.last_alerting[1], ",")}\n"
f"Last Alerting Time: {trigger.last_alerting[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n"
)
if user is not None and user.user_id == 265678699435655169: if user is not None and user.user_id == 265678699435655169:
alert_str += (f"\nShowing you some internals since you are the bot owner:\n" alert_str += (
f"```history.last_price_datum:\n" f"\nShowing you some internals since you are the bot owner:\n"
f"\t{history.last_price_datum[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n" f"```history.last_price_datum:\n"
f"\t{history.last_price_datum[1]}\n" f"\t{history.last_price_datum[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n"
f"trigger.last_alerting:\n" f"\t{history.last_price_datum[1]}\n"
f"\t{trigger.last_alerting[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n" f"trigger.last_alerting:\n"
f"\t{trigger.last_alerting[1]}\n" f"\t{trigger.last_alerting[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n"
f"trigger.last_trigger:\n" f"\t{trigger.last_alerting[1]}\n"
f"\t{trigger.last_trigger[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n" f"trigger.last_trigger:\n"
f"\t{trigger.last_trigger[1]}\n" f"\t{trigger.last_trigger[0].strftime('%Y-%m-%d %H:%M:%S UTC')}\n"
f"trigger.squelched:\n\t{trigger.squelched}```") f"\t{trigger.last_trigger[1]}\n"
f"trigger.squelched:\n\t{trigger.squelched}```"
)
else: else:
alert_str = "You should only be seeing this if the bot has not finished importing history at startup." alert_str = "You should only be seeing this if the bot has not finished importing history at startup."
fields.append( fields.append(
EmbedField( EmbedField(
name=f"{alert.to_human_string()} Alert", value=alert_str, inline=False)) name=f"{alert.to_human_string()} Alert",
value=alert_str,
inline=False,
)
)
embed = Embed( embed = Embed(
title=f"Alerts for {region.name} {flavor.name.lower().title()}", title=f"Alerts for {region.name} {flavor.name.lower().title()}",
color=0xb10000, color=0xB10000,
fields=fields fields=fields,
) )
return embed return embed

View File

@ -1,6 +1,9 @@
from interactions import ActionRow from interactions import ActionRow
from token_bot.ui.buttons.tracker.alert_category import HIGH_ALERT_BUTTON, LOW_ALERT_BUTTON from token_bot.ui.buttons.tracker.alert_category import (
HIGH_ALERT_BUTTON,
LOW_ALERT_BUTTON,
)
ALERT_TYPE_ROW: list[ActionRow] = [ ALERT_TYPE_ROW: list[ActionRow] = [
ActionRow( ActionRow(

View File

@ -1,67 +1,45 @@
from interactions import Button, ButtonStyle from interactions import Button, ButtonStyle
ATH_ADD_BUTTON = Button( ATH_ADD_BUTTON = Button(
custom_id='ath_add_button', custom_id="ath_add_button", style=ButtonStyle.GREEN, label="All Time High"
style=ButtonStyle.GREEN,
label="All Time High"
) )
ATL_ADD_BUTTON = Button( ATL_ADD_BUTTON = Button(
custom_id='atl_add_button', custom_id="atl_add_button", style=ButtonStyle.RED, label="All Time Low"
style=ButtonStyle.RED,
label="All Time Low"
) )
DH_ADD_BUTTON = Button( DH_ADD_BUTTON = Button(
custom_id='dh_add_button', custom_id="dh_add_button", style=ButtonStyle.GREEN, label="Daily High"
style=ButtonStyle.GREEN,
label="Daily High"
) )
DL_ADD_BUTTON = Button( DL_ADD_BUTTON = Button(
custom_id='dl_add_button', custom_id="dl_add_button", style=ButtonStyle.RED, label="Daily Low"
style=ButtonStyle.RED,
label="Daily Low"
) )
WH_ADD_BUTTON = Button( WH_ADD_BUTTON = Button(
custom_id='wh_add_button', custom_id="wh_add_button", style=ButtonStyle.GREEN, label="Weekly High"
style=ButtonStyle.GREEN,
label="Weekly High"
) )
WL_ADD_BUTTON = Button( WL_ADD_BUTTON = Button(
custom_id='wl_add_button', custom_id="wl_add_button", style=ButtonStyle.RED, label="Weekly Low"
style=ButtonStyle.RED,
label="Weekly Low"
) )
MH_ADD_BUTTON = Button( MH_ADD_BUTTON = Button(
custom_id='mh_add_button', custom_id="mh_add_button", style=ButtonStyle.GREEN, label="Monthly High"
style=ButtonStyle.GREEN,
label="Monthly High"
) )
ML_ADD_BUTTON = Button( ML_ADD_BUTTON = Button(
custom_id='ml_add_button', custom_id="ml_add_button", style=ButtonStyle.RED, label="Monthly Low"
style=ButtonStyle.RED,
label="Monthly Low"
) )
YH_ADD_BUTTON = Button( YH_ADD_BUTTON = Button(
custom_id='yh_add_button', custom_id="yh_add_button", style=ButtonStyle.GREEN, label="Yearly High"
style=ButtonStyle.GREEN,
label="Yearly High"
) )
YL_ADD_BUTTON = Button( YL_ADD_BUTTON = Button(
custom_id='yl_add_button', custom_id="yl_add_button", style=ButtonStyle.RED, label="Yearly Low"
style=ButtonStyle.RED,
label="Yearly Low"
) )
SP_ADD_BUTTON = Button( SP_ADD_BUTTON = Button(
custom_id='sp_add_button', custom_id="sp_add_button", style=ButtonStyle.GRAY, label="Custom Limit Price"
style=ButtonStyle.GRAY,
label="Custom Limit Price"
) )

View File

@ -1,19 +1,13 @@
from interactions import Button, ButtonStyle from interactions import Button, ButtonStyle
HIGH_ALERT_BUTTON = Button( HIGH_ALERT_BUTTON = Button(
custom_id='high_alert_button', custom_id="high_alert_button", style=ButtonStyle.GREEN, label="High Price Alert"
style=ButtonStyle.GREEN,
label="High Price Alert"
) )
LOW_ALERT_BUTTON = Button( LOW_ALERT_BUTTON = Button(
custom_id='low_alert_button', custom_id="low_alert_button", style=ButtonStyle.RED, label="Low Price Alert"
style=ButtonStyle.RED,
label="Low Price Alert"
) )
CUSTOM_ALERT_BUTTON = Button( CUSTOM_ALERT_BUTTON = Button(
custom_id='sp_add_button', custom_id="sp_add_button", style=ButtonStyle.GRAY, label="Custom Price Alert"
style=ButtonStyle.GRAY,
label="Custom Price Alert"
) )

View File

@ -1,19 +1,13 @@
from interactions import Button, ButtonStyle from interactions import Button, ButtonStyle
HIGH_ALERT = Button( HIGH_ALERT = Button(
custom_id='high_alert_button', custom_id="high_alert_button", style=ButtonStyle.GREEN, label="Add High Alert"
style=ButtonStyle.GREEN,
label="Add High Alert"
) )
LOW_ALERT = Button( LOW_ALERT = Button(
custom_id='low_alert_button', custom_id="low_alert_button", style=ButtonStyle.RED, label="Add Low Alert"
style=ButtonStyle.RED,
label="Add Low Alert"
) )
CUSTOM_ALERT = Button( CUSTOM_ALERT = Button(
custom_id='custom_alert_button', custom_id="custom_alert_button", style=ButtonStyle.GRAY, label="Add Custom Alert"
style=ButtonStyle.GRAY, )
label="Add Custom Alert"
)

View File

@ -1,15 +1,25 @@
from interactions import StringSelectMenu from interactions import StringSelectMenu
HIGH_ALERT_MENU = StringSelectMenu( HIGH_ALERT_MENU = StringSelectMenu(
"Daily High", "Weekly High", "Monthly High", "Yearly High", "All Time High", "Daily High",
"Weekly High",
"Monthly High",
"Yearly High",
"All Time High",
placeholder="Select a time period", placeholder="Select a time period",
min_values=1, max_values=1, min_values=1,
custom_id='high_alert_menu' max_values=1,
custom_id="high_alert_menu",
) )
LOW_ALERT_MENU = StringSelectMenu( LOW_ALERT_MENU = StringSelectMenu(
"Daily Low", "Weekly Low", "Monthly Low", "Yearly Low", "All Time Low", "Daily Low",
"Weekly Low",
"Monthly Low",
"Yearly Low",
"All Time Low",
placeholder="Select a time period", placeholder="Select a time period",
min_values=1, max_values=1, min_values=1,
custom_id='low_alert_menu' max_values=1,
) custom_id="low_alert_menu",
)

View File

@ -1,8 +1,10 @@
from interactions import StringSelectMenu from interactions import StringSelectMenu
FLAVOR_MENU = StringSelectMenu( FLAVOR_MENU = StringSelectMenu(
"Retail", "Classic", "Retail",
"Classic",
placeholder="Select version of WoW", placeholder="Select version of WoW",
min_values=1, max_values=1, min_values=1,
custom_id='flavor_menu' max_values=1,
) custom_id="flavor_menu",
)

View File

@ -1,8 +1,12 @@
from interactions import StringSelectMenu from interactions import StringSelectMenu
REGION_MENU = StringSelectMenu( REGION_MENU = StringSelectMenu(
"US", "EU", "KR", "TW", "US",
"EU",
"KR",
"TW",
placeholder="Select a region", placeholder="Select a region",
min_values=1, max_values=1, min_values=1,
custom_id='region_menu' max_values=1,
) custom_id="region_menu",
)