Skip to content

Commit

Permalink
Added favorite management using Telegram (#386)
Browse files Browse the repository at this point in the history
  • Loading branch information
floriegl committed Jul 27, 2023
1 parent 8b6ea89 commit 828ff80
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 11 deletions.
4 changes: 3 additions & 1 deletion src/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from models.config import Config
from models.cron import Cron
from models.favorites import Favorites
from models.item import Item
from models.location import Location
from models.metrics import Metrics
from models.reservations import Reservations

__all__ = ['Config', 'Cron', 'Item', 'Metrics', 'Location', 'Reservations']
__all__ = ['Config', 'Cron', 'Favorites', 'Item',
'Metrics', 'Location', 'Reservations']
79 changes: 79 additions & 0 deletions src/models/favorites.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import logging
from dataclasses import dataclass
from typing import List

from models.errors import TgtgAPIError
from models.item import Item
from tgtg import TgtgClient

log = logging.getLogger("tgtg")


@dataclass
class AddFavoriteRequest():
item_id: str
item_display_name: str
proceed: bool


@dataclass
class RemoveFavoriteRequest():
item_id: str
item_display_name: str
proceed: bool


class Favorites():
def __init__(self, client: TgtgClient) -> None:
self.client = client

def is_item_favorite(self, item_id: str) -> bool:
"""Returns true if the provided item ID is in the favorites
Args:
item_id (str): Item ID
Returns:
bool: true, if the provided item ID is in the favorites
"""
return any(item for item
in self.client.get_favorites()
if Item(item).item_id == item_id)

def get_item_by_id(self, item_id: str) -> Item:
"""Gets an item by the Item ID
Args:
item_id (str): Item ID
Returns:
Item: the Item for the Item ID or an empty Item
"""
try:
return Item(self.client.get_item(item_id))
except TgtgAPIError:
return Item({})

def get_favorites(self) -> List[Item]:
"""Get all favorite items
Return:
List: List of favorite items
"""
return [Item(item) for item in self.client.get_favorites()]

def add_favorites(self, item_ids: List[str]) -> None:
"""Adds all the provided item IDs to the favorites
Args:
item_ids (str): Item ID list
"""
for item_id in item_ids:
self.client.set_favorite(item_id, True)

def remove_favorite(self, item_ids: List[str]) -> None:
"""Removes all the provided item IDs from the favorites
Args:
item_ids (str): Item ID list
"""
for item_id in item_ids:
self.client.set_favorite(item_id, False)
7 changes: 4 additions & 3 deletions src/notifiers/notifiers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from typing import List

from models import Config, Cron, Item, Reservations
from models import Config, Cron, Favorites, Item, Reservations
from models.reservations import Reservation
from notifiers.apprise import Apprise
from notifiers.base import Notifier
Expand All @@ -18,7 +18,8 @@


class Notifiers:
def __init__(self, config: Config, reservations: Reservations):
def __init__(self, config: Config, reservations: Reservations,
favorites: Favorites):
self._notifiers: List[Notifier] = [
Apprise(config),
Console(config),
Expand All @@ -27,7 +28,7 @@ def __init__(self, config: Config, reservations: Reservations):
IFTTT(config),
Ntfy(config),
WebHook(config),
Telegram(config, reservations),
Telegram(config, reservations, favorites),
Script(config),
]
log.info("Activated notifiers:")
Expand Down
157 changes: 152 additions & 5 deletions src/notifiers/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
from telegram.bot import BotCommand
from telegram.error import BadRequest, NetworkError, TelegramError, TimedOut
from telegram.ext import (CallbackContext, CallbackQueryHandler,
CommandHandler, Updater)
CommandHandler, Filters, MessageHandler, Updater)
from telegram.utils.helpers import escape_markdown

from models import Config, Item, Reservations
from models import Config, Favorites, Item, Reservations
from models.errors import MaskConfigurationError, TelegramConfigurationError
from models.favorites import AddFavoriteRequest, RemoveFavoriteRequest
from models.reservations import Order, Reservation
from notifiers.base import Notifier

Expand All @@ -25,7 +26,8 @@ class Telegram(Notifier):
"""
MAX_RETRIES = 10

def __init__(self, config: Config, reservations: Reservations):
def __init__(self, config: Config, reservations: Reservations,
favorites: Favorites):
self.updater = None
self.config = config
self.enabled = config.telegram.get("enabled", False)
Expand All @@ -40,6 +42,7 @@ def __init__(self, config: Config, reservations: Reservations):
self.mute = None
self.retries = 0
self.reservations = reservations
self.favorites = favorites
if self.enabled and (not self.token or not self.body):
raise TelegramConfigurationError()
if self.enabled:
Expand All @@ -64,7 +67,15 @@ def __init__(self, config: Config, reservations: Reservations):
CommandHandler("reservations", self._cancel_reservations_menu),
CommandHandler("orders", self._cancel_orders_menu),
CommandHandler("cancelall", self._cancel_all_orders),
CallbackQueryHandler(self._callback_query_handler)]
CommandHandler("listfavorites", self._list_favorites),
CommandHandler("listfavoriteids", self._list_favorite_ids),
CommandHandler("addfavorites", self._add_favorites),
CommandHandler("removefavorites", self._remove_favorites),
MessageHandler(Filters.regex(
r'^https:\/\/share\.toogoodtogo\.com\/item\/(\d+)\/?'
), self._url_handler),
CallbackQueryHandler(self._callback_query_handler)
]
for handler in handlers:
self.updater.dispatcher.add_handler(handler)
self.updater.dispatcher.add_error_handler(self._error)
Expand All @@ -76,7 +87,12 @@ def __init__(self, config: Config, reservations: Reservations):
BotCommand('reserve', 'Reserve the next available Magic Bag'),
BotCommand('reservations', 'List and cancel Reservations'),
BotCommand('orders', 'List and cancel active Orders'),
BotCommand('cancelall', 'Cancels all active orders')
BotCommand('cancelall', 'Cancels all active orders'),
BotCommand('listfavorites', 'List all favorites'),
BotCommand('listfavoriteids',
'List all item ids from favorites'),
BotCommand('addfavorites', 'Add item ids to favorites'),
BotCommand('removefavorites', 'Remove Item ids from favorites')
])
if not self.disable_commands:
self.updater.start_polling()
Expand Down Expand Up @@ -225,6 +241,118 @@ def _cancel_all_orders(self,
update.message.reply_text("Cancelled all active Orders")
log.debug("Cancelled all active Orders")

def _list_favorites(self,
update: Update,
context: CallbackContext) -> None:
del context
favorites = self.favorites.get_favorites()
if not favorites:
update.message.reply_text(
"You currently don't have any favorites.")
else:
update.message.reply_text(
"\n".join([f"• {item.item_id} - {item.display_name}"
for item in favorites]))

def _list_favorite_ids(self,
update: Update,
context: CallbackContext) -> None:
del context
favorites = self.favorites.get_favorites()
if not favorites:
update.message.reply_text(
"You currently don't have any favorites.")
else:
update.message.reply_text(
" ".join([item.item_id for item in favorites]))

def _add_favorites(self,
update: Update,
context: CallbackContext) -> None:
if not context.args:
update.message.reply_text(
"Please supply item ids in one of the following ways: " +
"'/addfavorites 12345 23456 34567' or " +
"'/addfavorites 12345,23456,34567'")
return

item_ids = list(filter(bool, map(str.strip,
[split_args for arg in context.args
for split_args in arg.split(",")]
)))
self.favorites.add_favorites(item_ids)
update.message.reply_text(
f"Added the following item ids to favorites: {' '.join(item_ids)}")
log.debug('Added the following item ids to favorites: "%s"', item_ids)

def _remove_favorites(self,
update: Update,
context: CallbackContext) -> None:
if not context.args:
update.message.reply_text(
"Please supply item ids in one of the following ways: " +
"'/removefavorites 12345 23456 34567' or " +
"'/removefavorites 12345,23456,34567'")
return

item_ids = list(filter(bool, map(str.strip,
[split_args for arg in context.args
for split_args in arg.split(",")]
)))
self.favorites.remove_favorite(item_ids)
update.message.reply_text(
"Removed the following item ids from favorites: "
+ f"{' '.join(item_ids)}")
log.debug('Removed the following item ids from favorites: '
+ '"%s"', item_ids)

def _url_handler(self,
update: Update,
context: CallbackContext) -> None:
item_id = context.matches[0].group(1)
item_favorite = self.favorites.is_item_favorite(item_id)
item = self.favorites.get_item_by_id(item_id)
if item.item_id is None:
update.message.reply_text("There is no Item with this link")
return

if item_favorite:
update.message.reply_text(
f"{item.display_name} is in your favorites. " +
"Do you want to remove it?",
reply_markup=(InlineKeyboardMarkup([[
InlineKeyboardButton("Yes",
callback_data=RemoveFavoriteRequest(
item_id,
item.display_name,
True
)),
InlineKeyboardButton("No",
callback_data=RemoveFavoriteRequest(
item_id,
item.display_name,
False
))
]])))
else:
update.message.reply_text(
f"{item.display_name} is not in your favorites. " +
"Do you want to add it?",
reply_markup=(InlineKeyboardMarkup([[
InlineKeyboardButton("Yes",
callback_data=AddFavoriteRequest(
item_id,
item.display_name,
True
)),
InlineKeyboardButton("No",
callback_data=AddFavoriteRequest(
item_id,
item.display_name,
False
))
]])))

def _callback_query_handler(self,
update: Update,
context: CallbackContext) -> None:
Expand All @@ -247,6 +375,25 @@ def _callback_query_handler(self,
update.callback_query.answer(
f"Canceled Order for {data.display_name}")
log.debug('Canceled order for "%s"', data.display_name)
if isinstance(data, AddFavoriteRequest):
if data.proceed:
self.favorites.add_favorites([data.item_id])
update.callback_query.edit_message_text(
f"Added {data.item_display_name} to favorites")
log.debug('Added "%s" to favorites', data.item_display_name)
log.debug('Removed "%s" from favorites',
data.item_display_name)
else:
update.callback_query.delete_message()
if isinstance(data, RemoveFavoriteRequest):
if data.proceed:
self.favorites.remove_favorite([data.item_id])
update.callback_query.edit_message_text(
f"Removed {data.item_display_name} from favorites")
log.debug('Removed "%s" from favorites',
data.item_display_name)
else:
update.callback_query.delete_message()

def _error(self, update: Update, context: CallbackContext) -> None:
"""Log Errors caused by Updates."""
Expand Down
7 changes: 5 additions & 2 deletions src/scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

from progress.spinner import Spinner

from models import Config, Cron, Item, Location, Metrics, Reservations
from models import (Config, Cron, Favorites, Item, Location, Metrics,
Reservations)
from models.errors import TgtgAPIError
from notifiers import Notifiers
from tgtg import TgtgClient
Expand Down Expand Up @@ -59,6 +60,7 @@ def __init__(self, config: Config):
datadome_cookie=self.config.tgtg.get("datadome")
)
self.reservations = Reservations(self.tgtg_client)
self.favorites = Favorites(self.tgtg_client)

def _get_test_item(self) -> Item:
"""
Expand Down Expand Up @@ -186,7 +188,8 @@ def run(self) -> NoReturn:
# activate and test notifiers
if self.config.metrics:
self.metrics.enable_metrics()
self.notifiers = Notifiers(self.config, self.reservations)
self.notifiers = Notifiers(
self.config, self.reservations, self.favorites)
if not self.config.disable_tests and \
self.notifiers.notifier_count > 0:
log.info("Sending test Notifications ...")
Expand Down
Loading

0 comments on commit 828ff80

Please sign in to comment.