Initial implementation of custom price triggers
- likely to have some bugs, but this is good enough for a preview release.
This commit is contained in:
parent
19eb0a4e24
commit
ed79f4b65c
@ -41,3 +41,18 @@ 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)
|
||||||
|
|
||||||
|
async def get_all_by_type(self, alert_type: int) -> List[Alert]:
|
||||||
|
"""Query all alerts of a specific type from the database."""
|
||||||
|
from aiodynamo.expressions import F
|
||||||
|
alerts = []
|
||||||
|
async for item in self.table.query(
|
||||||
|
key_condition=F("alert").equals(alert_type)
|
||||||
|
):
|
||||||
|
alert = Alert.from_item(
|
||||||
|
primary_key=item["alert"],
|
||||||
|
sort_key=item["flavor-region-price"],
|
||||||
|
users=item.get("users", [])
|
||||||
|
)
|
||||||
|
alerts.append(alert)
|
||||||
|
return alerts
|
||||||
|
|||||||
@ -15,10 +15,13 @@ class History:
|
|||||||
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] = []
|
||||||
|
# Create triggers for all non-custom alert types
|
||||||
for alert_type in AlertType:
|
for alert_type in AlertType:
|
||||||
|
if alert_type != AlertType.SPECIFIC_PRICE:
|
||||||
self._update_triggers.append(
|
self._update_triggers.append(
|
||||||
UpdateTrigger(Alert(alert_type, flavor, self._region))
|
UpdateTrigger(Alert(alert_type, flavor, self._region))
|
||||||
)
|
)
|
||||||
|
# SPECIFIC_PRICE triggers are created on-demand as they have unique prices
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def flavor(self) -> Flavor:
|
def flavor(self) -> Flavor:
|
||||||
@ -55,8 +58,21 @@ class History:
|
|||||||
self._history.append(datum)
|
self._history.append(datum)
|
||||||
return await self._process_update_triggers()
|
return await self._process_update_triggers()
|
||||||
|
|
||||||
async def find_update_trigger_from_alert(self, alert: Alert) -> UpdateTrigger:
|
async def find_update_trigger_from_alert(self, alert: Alert, initial_import: bool = False) -> UpdateTrigger:
|
||||||
for trigger in self._update_triggers:
|
for trigger in self._update_triggers:
|
||||||
if trigger.alert == alert:
|
if trigger.alert == alert:
|
||||||
return trigger
|
return trigger
|
||||||
|
|
||||||
|
# If not found and it's a SPECIFIC_PRICE alert, create it on-demand
|
||||||
|
if alert.alert_type == AlertType.SPECIFIC_PRICE:
|
||||||
|
new_trigger = UpdateTrigger(alert)
|
||||||
|
if initial_import:
|
||||||
|
new_trigger.squelched = True
|
||||||
|
self._update_triggers.append(new_trigger)
|
||||||
|
# Initialize the trigger with current history
|
||||||
|
if self._latest_price_datum is not None:
|
||||||
|
new_trigger.check_and_update(self._latest_price_datum, self._history)
|
||||||
|
new_trigger.squelched = False
|
||||||
|
return new_trigger
|
||||||
|
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|||||||
@ -57,6 +57,16 @@ class HistoryManager:
|
|||||||
await history.add_price(item)
|
await history.add_price(item)
|
||||||
self._history[flavor][region] = history
|
self._history[flavor][region] = history
|
||||||
|
|
||||||
|
async def load_custom_alerts(self, custom_alerts: List[Alert]):
|
||||||
|
"""Load custom price alerts and initialize their triggers with historical data."""
|
||||||
|
for alert in custom_alerts:
|
||||||
|
history = self._history[alert.flavor][alert.region]
|
||||||
|
# This will create the trigger on-demand via find_update_trigger_from_alert
|
||||||
|
trigger = await history.find_update_trigger_from_alert(alert)
|
||||||
|
# Process all historical data through this trigger to initialize its state
|
||||||
|
for datum in history.history:
|
||||||
|
trigger.check_and_update(datum, history.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)
|
||||||
|
|||||||
@ -29,6 +29,10 @@ class UpdateTrigger:
|
|||||||
def squelched(self):
|
def squelched(self):
|
||||||
return self._squelched
|
return self._squelched
|
||||||
|
|
||||||
|
@squelched.setter
|
||||||
|
def squelched(self, value):
|
||||||
|
self._squelched = value
|
||||||
|
|
||||||
def _find_next_trigger(
|
def _find_next_trigger(
|
||||||
self,
|
self,
|
||||||
comparison_operator: Callable,
|
comparison_operator: Callable,
|
||||||
@ -93,12 +97,47 @@ class UpdateTrigger:
|
|||||||
case AlertType.ALL_TIME_HIGH:
|
case AlertType.ALL_TIME_HIGH:
|
||||||
time_range = now - start_time
|
time_range = now - start_time
|
||||||
comparison_operator = operator.gt
|
comparison_operator = operator.gt
|
||||||
|
case AlertType.SPECIFIC_PRICE:
|
||||||
|
# For custom price alerts, check if the price crosses the threshold
|
||||||
|
# We alert when price moves from below to above (or vice versa)
|
||||||
|
target_price = self._alert.price
|
||||||
|
if self._last_trigger is None:
|
||||||
|
# First time - check if current price crossed the threshold
|
||||||
|
if new_datum[1] >= target_price:
|
||||||
|
self._last_trigger = new_datum
|
||||||
|
self._last_alerting = new_datum
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
# Check if we crossed the threshold
|
||||||
|
old_price = self._last_trigger[1]
|
||||||
|
new_price = new_datum[1]
|
||||||
|
|
||||||
|
# Alert if we cross the threshold in either direction
|
||||||
|
crossed_up = old_price < target_price <= new_price
|
||||||
|
crossed_down = old_price >= target_price > new_price
|
||||||
|
|
||||||
|
if crossed_up or crossed_down:
|
||||||
|
self._last_trigger = new_datum
|
||||||
|
self._last_alerting = new_datum
|
||||||
|
was_squelched = self._squelched
|
||||||
|
self._squelched = True
|
||||||
|
return not was_squelched
|
||||||
|
elif self._squelched:
|
||||||
|
# Update last_trigger but don't alert (we're tracking current price)
|
||||||
|
self._last_trigger = new_datum
|
||||||
|
# If we moved away from threshold, allow next crossing to alert
|
||||||
|
if (crossed_up and new_price < target_price) or (crossed_down and new_price >= target_price):
|
||||||
|
self._squelched = False
|
||||||
|
else:
|
||||||
|
# Just update the last trigger for tracking
|
||||||
|
self._last_trigger = new_datum
|
||||||
|
return False
|
||||||
case _:
|
case _:
|
||||||
# TODO: The logic here is certainly wrong for Custom
|
|
||||||
time_range = datetime.timedelta(days=int(365.25 * 6))
|
time_range = datetime.timedelta(days=int(365.25 * 6))
|
||||||
comparison_operator = operator.eq
|
comparison_operator = operator.eq
|
||||||
|
|
||||||
if new_datum[0] > now - time_range:
|
if new_datum[0] > now - time_range and self._alert.alert_type != AlertType.SPECIFIC_PRICE:
|
||||||
if self._last_trigger is None:
|
if self._last_trigger is None:
|
||||||
self._last_trigger = new_datum
|
self._last_trigger = new_datum
|
||||||
self._last_alerting = new_datum
|
self._last_alerting = new_datum
|
||||||
|
|||||||
@ -99,7 +99,8 @@ class Alert:
|
|||||||
|
|
||||||
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
|
price_gold = self.price
|
||||||
|
return f"Custom Price: {format(price_gold, ',')}g"
|
||||||
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()}"
|
||||||
|
|||||||
@ -38,4 +38,7 @@ class AlertType(Enum):
|
|||||||
case "All Time Low":
|
case "All Time Low":
|
||||||
return AlertType.ALL_TIME_LOW
|
return AlertType.ALL_TIME_LOW
|
||||||
case _:
|
case _:
|
||||||
|
# Check if it's a custom price format like "Custom Price: 250,000g"
|
||||||
|
if category.startswith("Custom Price"):
|
||||||
|
return AlertType.SPECIFIC_PRICE
|
||||||
return AlertType.SPECIFIC_PRICE
|
return AlertType.SPECIFIC_PRICE
|
||||||
|
|||||||
@ -18,6 +18,10 @@ from interactions import (
|
|||||||
is_owner,
|
is_owner,
|
||||||
check,
|
check,
|
||||||
StringSelectOption, integration_types,
|
StringSelectOption, integration_types,
|
||||||
|
Modal,
|
||||||
|
ShortText,
|
||||||
|
modal_callback,
|
||||||
|
ModalContext,
|
||||||
)
|
)
|
||||||
from interactions import Task, IntervalTrigger
|
from interactions import Task, IntervalTrigger
|
||||||
from interactions import slash_command, listen
|
from interactions import slash_command, listen
|
||||||
@ -142,6 +146,13 @@ class Tracker(Extension):
|
|||||||
self.bot.logger.log(
|
self.bot.logger.log(
|
||||||
logging.INFO, "TokenBot Tracker: Loading Historical Data Finished"
|
logging.INFO, "TokenBot Tracker: Loading Historical Data Finished"
|
||||||
)
|
)
|
||||||
|
self.bot.logger.log(logging.INFO, "TokenBot Tracker: Loading Custom Price Alerts")
|
||||||
|
# Load all SPECIFIC_PRICE alerts from database (AlertType.SPECIFIC_PRICE = 11)
|
||||||
|
custom_alerts = await self._alerts.get_all_by_type(AlertType.SPECIFIC_PRICE.value)
|
||||||
|
await self._history_manager.load_custom_alerts(custom_alerts)
|
||||||
|
self.bot.logger.log(
|
||||||
|
logging.INFO, f"TokenBot Tracker: Loaded {len(custom_alerts)} Custom Price Alerts"
|
||||||
|
)
|
||||||
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()
|
||||||
|
|
||||||
@ -206,20 +217,20 @@ class Tracker(Extension):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
flavor = await self.flavor_select_menu(ctx)
|
flavor = await self.flavor_select_menu(ctx)
|
||||||
alert_category = await self.alert_category_select_menu(ctx)
|
alert_category, price = await self.alert_category_select_menu(ctx)
|
||||||
match alert_category:
|
match alert_category:
|
||||||
case AlertCategory.LOW:
|
case AlertCategory.LOW:
|
||||||
alert_type = await self.low_alert_select_menu(ctx)
|
alert_type = await self.low_alert_select_menu(ctx)
|
||||||
case AlertCategory.HIGH:
|
case AlertCategory.HIGH:
|
||||||
alert_type = await self.high_alert_select_menu(ctx)
|
alert_type = await self.high_alert_select_menu(ctx)
|
||||||
case _:
|
case AlertCategory.CUSTOM:
|
||||||
raise NotImplementedError
|
alert_type = AlertType.SPECIFIC_PRICE
|
||||||
|
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
return
|
return
|
||||||
|
|
||||||
else:
|
else:
|
||||||
alert = Alert(alert_type, flavor, user.region)
|
alert = Alert(alert_type, flavor, user.region, price)
|
||||||
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),
|
||||||
@ -287,24 +298,8 @@ class Tracker(Extension):
|
|||||||
# Callbacks Commands #
|
# Callbacks Commands #
|
||||||
###################################
|
###################################
|
||||||
|
|
||||||
@component_callback("flavor_menu")
|
# Note: Callbacks for flavor_menu, high_alert_menu, low_alert_menu, and alert buttons
|
||||||
async def flavor_menu(self, ctx: ComponentContext):
|
# are disabled because they interfere with wait_for_component manual handling
|
||||||
await ctx.send(f"Selected Flavor: {ctx.values[0]}", ephemeral=True)
|
|
||||||
|
|
||||||
@component_callback("high_alert_menu")
|
|
||||||
async def high_alert_menu(self, ctx: ComponentContext):
|
|
||||||
await ctx.send(f"Selected Alert: {ctx.values[0]}", ephemeral=True)
|
|
||||||
|
|
||||||
@component_callback("low_alert_menu")
|
|
||||||
async def low_alert_menu(self, ctx: ComponentContext):
|
|
||||||
await ctx.send(f"Selected Alert: {ctx.values[0]}", ephemeral=True)
|
|
||||||
|
|
||||||
@component_callback("remove_alert_menu")
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
@component_callback("region_menu")
|
@component_callback("region_menu")
|
||||||
async def region_menu_cb(self, ctx: ComponentContext):
|
async def region_menu_cb(self, ctx: ComponentContext):
|
||||||
@ -316,18 +311,6 @@ class Tracker(Extension):
|
|||||||
)
|
)
|
||||||
await ctx.defer(edit_origin=True, suppress_error=True)
|
await ctx.defer(edit_origin=True, suppress_error=True)
|
||||||
|
|
||||||
@component_callback("high_alert_button")
|
|
||||||
async def high_alert_button(self, ctx: ComponentContext):
|
|
||||||
await ctx.send("You selected to add a High Price Alert", ephemeral=True)
|
|
||||||
|
|
||||||
@component_callback("low_alert_button")
|
|
||||||
async def low_alert_button(self, ctx: ComponentContext):
|
|
||||||
await ctx.send("You selected to add a Low Price Alert", ephemeral=True)
|
|
||||||
|
|
||||||
@component_callback("custom_alert_button")
|
|
||||||
async def custom_alert_button(self, ctx: ComponentContext):
|
|
||||||
await ctx.send("You selected to add a Custom Price Alert", ephemeral=True)
|
|
||||||
|
|
||||||
###################################
|
###################################
|
||||||
# Helper Functions #
|
# Helper Functions #
|
||||||
###################################
|
###################################
|
||||||
@ -384,8 +367,19 @@ 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_str = " ".join(selection_split[1:])
|
||||||
return Alert(alert_type, flavor, user.region)
|
alert_type = AlertType.from_str(alert_type_str)
|
||||||
|
|
||||||
|
# Parse price for custom alerts
|
||||||
|
price = 0
|
||||||
|
if alert_type == AlertType.SPECIFIC_PRICE:
|
||||||
|
# Extract price from "Custom Price: 250,000g"
|
||||||
|
price_part = alert_type_str.split(": ")[1].rstrip("g").replace(",", "")
|
||||||
|
price_gold = int(price_part)
|
||||||
|
# Convert gold to copper
|
||||||
|
price = price_gold
|
||||||
|
|
||||||
|
return Alert(alert_type, flavor, user.region, price)
|
||||||
|
|
||||||
async def region_select_menu(self, ctx: SlashContext, user: User | None = None):
|
async def region_select_menu(self, ctx: SlashContext, user: User | None = None):
|
||||||
region_menu = copy.deepcopy(REGION_MENU)
|
region_menu = copy.deepcopy(REGION_MENU)
|
||||||
@ -448,7 +442,7 @@ 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) -> tuple[AlertCategory, int]:
|
||||||
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", components=alert_type_button, ephemeral=True
|
"Select an alert type to add", components=alert_type_button, ephemeral=True
|
||||||
@ -464,14 +458,21 @@ class Tracker(Extension):
|
|||||||
context=ctx, components=alert_type_button, content="Timed out"
|
context=ctx, components=alert_type_button, content="Timed out"
|
||||||
)
|
)
|
||||||
raise TimeoutError
|
raise TimeoutError
|
||||||
|
else:
|
||||||
|
alert_type = AlertCategory.from_str(alert_type_component.ctx.custom_id)
|
||||||
|
|
||||||
|
# If custom alert, send modal as response to button press
|
||||||
|
if alert_type == AlertCategory.CUSTOM:
|
||||||
|
price = await self.custom_price_modal(alert_type_component.ctx)
|
||||||
else:
|
else:
|
||||||
# Acknowledge the component interaction to avoid 404 Unknown Interaction
|
# Acknowledge the component interaction to avoid 404 Unknown Interaction
|
||||||
await alert_type_component.ctx.defer(edit_origin=True, suppress_error=True)
|
await alert_type_component.ctx.defer(edit_origin=True, suppress_error=True)
|
||||||
alert_type = AlertCategory.from_str(alert_type_component.ctx.custom_id)
|
price = 0
|
||||||
|
|
||||||
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)
|
await alert_type_message.edit(context=ctx, components=alert_type_button)
|
||||||
return alert_type
|
return alert_type, price
|
||||||
|
|
||||||
async def _alert_select_menu_handler(
|
async def _alert_select_menu_handler(
|
||||||
self, ctx: SlashContext, menu: StringSelectMenu, message: Message
|
self, ctx: SlashContext, menu: StringSelectMenu, message: Message
|
||||||
@ -508,6 +509,37 @@ class Tracker(Extension):
|
|||||||
)
|
)
|
||||||
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 custom_price_modal(self, ctx: ComponentContext) -> int:
|
||||||
|
modal = Modal(
|
||||||
|
ShortText(
|
||||||
|
label="Price (in gold)",
|
||||||
|
custom_id="price_input",
|
||||||
|
placeholder="e.g., 250000 for 250k gold",
|
||||||
|
required=True,
|
||||||
|
),
|
||||||
|
title="Custom Price Alert",
|
||||||
|
custom_id="custom_price_modal",
|
||||||
|
)
|
||||||
|
await ctx.send_modal(modal)
|
||||||
|
|
||||||
|
try:
|
||||||
|
modal_ctx: ModalContext = await self.bot.wait_for_modal(modal, timeout=300)
|
||||||
|
except TimeoutError:
|
||||||
|
await ctx.send("Modal timed out", ephemeral=True)
|
||||||
|
raise TimeoutError
|
||||||
|
else:
|
||||||
|
price_str = modal_ctx.responses["price_input"]
|
||||||
|
try:
|
||||||
|
price_gold = int(price_str.replace(",", "").replace(" ", "").replace("g", ""))
|
||||||
|
if price_gold <= 0:
|
||||||
|
await modal_ctx.send("Price must be greater than 0", ephemeral=True)
|
||||||
|
# Convert gold to copper (1 gold = 10000 copper)
|
||||||
|
price_copper = price_gold
|
||||||
|
await modal_ctx.send(f"Custom price alert set for {format(price_gold, ',')}g", ephemeral=True)
|
||||||
|
return price_copper
|
||||||
|
except ValueError:
|
||||||
|
await modal_ctx.send("Invalid price. Please enter a valid number.", ephemeral=True)
|
||||||
|
|
||||||
async def _user_is_registered(self, ctx: SlashContext) -> bool:
|
async def _user_is_registered(self, ctx: SlashContext) -> bool:
|
||||||
if not await self._users.exists(ctx.user.id):
|
if not await self._users.exists(ctx.user.id):
|
||||||
await ctx.send(
|
await ctx.send(
|
||||||
@ -547,6 +579,19 @@ class Tracker(Extension):
|
|||||||
f"\t{trigger.last_trigger[1]}\n"
|
f"\t{trigger.last_trigger[1]}\n"
|
||||||
f"trigger.squelched:\n\t{trigger.squelched}```"
|
f"trigger.squelched:\n\t{trigger.squelched}```"
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# For custom price alerts, show current status vs threshold
|
||||||
|
if alert.alert_type == AlertType.SPECIFIC_PRICE:
|
||||||
|
current_price = history.last_price_datum[1]
|
||||||
|
target_price_gold = alert.price
|
||||||
|
current_price_gold = current_price
|
||||||
|
|
||||||
|
alert_str = (
|
||||||
|
f"Threshold has never been crossed\n"
|
||||||
|
f"Current Price: {format(current_price_gold, ',')}g\n"
|
||||||
|
f"Target Price: {format(target_price_gold, ',')}g\n"
|
||||||
|
f"[Link to this Chart]({self._render_token_url(alert, time_range='72h')})\n"
|
||||||
|
)
|
||||||
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(
|
||||||
@ -563,7 +608,7 @@ class Tracker(Extension):
|
|||||||
)
|
)
|
||||||
return embed
|
return embed
|
||||||
|
|
||||||
def _render_token_url(self, alert: Alert) -> str:
|
def _render_token_url(self, alert: Alert, time_range: str | None = None) -> str:
|
||||||
match alert.flavor:
|
match alert.flavor:
|
||||||
case Flavor.CLASSIC:
|
case Flavor.CLASSIC:
|
||||||
url = "https://classic.wowtoken.app/?"
|
url = "https://classic.wowtoken.app/?"
|
||||||
@ -572,6 +617,12 @@ class Tracker(Extension):
|
|||||||
case _:
|
case _:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
url += f"region={alert.region.value}&"
|
url += f"region={alert.region.value}&"
|
||||||
|
|
||||||
|
# If time_range is explicitly provided, use it
|
||||||
|
if time_range:
|
||||||
|
url += f"time={time_range}&"
|
||||||
|
else:
|
||||||
|
# Otherwise, determine time range based on alert type
|
||||||
match alert.alert_type:
|
match alert.alert_type:
|
||||||
case AlertType.WEEKLY_LOW | AlertType.WEEKLY_HIGH:
|
case AlertType.WEEKLY_LOW | AlertType.WEEKLY_HIGH:
|
||||||
url += "time=168h&"
|
url += "time=168h&"
|
||||||
@ -581,5 +632,7 @@ class Tracker(Extension):
|
|||||||
url += "time=1y&"
|
url += "time=1y&"
|
||||||
case AlertType.ALL_TIME_LOW | AlertType.ALL_TIME_HIGH:
|
case AlertType.ALL_TIME_LOW | AlertType.ALL_TIME_HIGH:
|
||||||
url += "time=all&"
|
url += "time=all&"
|
||||||
|
case _:
|
||||||
|
url += "time=72h&"
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|||||||
@ -3,11 +3,13 @@ from interactions import ActionRow
|
|||||||
from token_bot.ui.buttons.tracker.alert_category import (
|
from token_bot.ui.buttons.tracker.alert_category import (
|
||||||
HIGH_ALERT_BUTTON,
|
HIGH_ALERT_BUTTON,
|
||||||
LOW_ALERT_BUTTON,
|
LOW_ALERT_BUTTON,
|
||||||
|
CUSTOM_ALERT_BUTTON,
|
||||||
)
|
)
|
||||||
|
|
||||||
ALERT_TYPE_ROW: list[ActionRow] = [
|
ALERT_TYPE_ROW: list[ActionRow] = [
|
||||||
ActionRow(
|
ActionRow(
|
||||||
HIGH_ALERT_BUTTON,
|
HIGH_ALERT_BUTTON,
|
||||||
LOW_ALERT_BUTTON,
|
LOW_ALERT_BUTTON,
|
||||||
|
CUSTOM_ALERT_BUTTON,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user