From 29813b0419db2d1d95db2376885ad794d4c3dcd6 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sun, 25 Jun 2023 20:39:50 +1000 Subject: [PATCH 1/4] Added dominos coupons command --- uqcsbot/__main__.py | 1 + uqcsbot/dominos_coupons.py | 153 +++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 uqcsbot/dominos_coupons.py diff --git a/uqcsbot/__main__.py b/uqcsbot/__main__.py index c6c3033..48a0f39 100644 --- a/uqcsbot/__main__.py +++ b/uqcsbot/__main__.py @@ -45,6 +45,7 @@ async def main(): "basic", "cat", "cowsay", + "dominos_coupons", "error_handler", "events", "gaming", diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py new file mode 100644 index 0000000..6982094 --- /dev/null +++ b/uqcsbot/dominos_coupons.py @@ -0,0 +1,153 @@ +from datetime import datetime +from typing import Optional +import logging +import requests +from requests.exceptions import RequestException +from bs4 import BeautifulSoup + +import discord +from discord import app_commands +from discord.ext import commands + +from uqcsbot.bot import UQCSBot + + +MAX_COUPONS = 10 # Prevents abuse +COUPONESE_DOMINOS_URL = "https://www.couponese.com/store/dominos.com.au/" + + +class DominosCoupons(commands.Cog): + def __init__(self, bot: UQCSBot): + self.bot = bot + + @app_commands.command() + @app_commands.describe( + number_of_coupons="The number of coupons to return. Defaults to 5 with max 10.", + ignore_expiry="Indicates to include coupons that have expired. Defaults to True.", + keywords="Words to search for within the coupon. All coupons descriptions will mention at least one keyword.", + ) + async def dominoscoupons( + self, + interaction: discord.Interaction, + number_of_coupons: Optional[int] = 5, + ignore_expiry: Optional[bool] = True, + keywords: Optional[str] = "", + ): + """ + Returns a list of dominos coupons + """ + await interaction.response.defer(thinking=True) + + if number_of_coupons < 1 or number_of_coupons > MAX_COUPONS: + await interaction.edit_original_response( + content=f"You can't have that many coupons. Try a number between 1 and {MAX_COUPONS}." + ) + return + try: + coupons = _get_coupons(number_of_coupons, ignore_expiry, keywords.split()) + except RequestException as error: + logging.warning( + f"Could not connect to dominos coupon site ({COUPONESE_DOMINOS_URL}): {error.response.content}" + ) + await interaction.edit_original_response( + content=f"Sadly could not reach the coupon website (<{COUPONESE_DOMINOS_URL}>)..." + ) + return + except HTTPResponseException as error: + logging.warning( + f"Received a HTTP response code that was not OK (200), namely ({error.http_code}). Error information: {error.message}" + ) + await interaction.edit_original_response( + content=f"Could not find the coupons on the coupon website (<{COUPONESE_DOMINOS_URL}>)..." + ) + return + + if not coupons: + await interaction.edit_original_response( + content=f"Could not find any coupons matching the given arguments from the coupon website (<{COUPONESE_DOMINOS_URL}>)." + ) + return + + message = "Domino's Coupons:\n" + for coupon in coupons: + message += f"`{coupon.code}` - {coupon.description} *[Expires: {coupon.expiry_date}]*\n" + await interaction.edit_original_response(content=message) + + +class Coupon: + def __init__(self, code: str, expiry_date: str, description: str) -> None: + self.code = code + self.expiry_date = expiry_date + self.description = description + + def is_valid(self) -> bool: + try: + expiry_date = datetime.strptime(self.expiry_date, "%Y-%m-%d") + now = datetime.now() + return all( + [ + expiry_date.year >= now.year, + expiry_date.month >= now.month, + expiry_date.day >= now.day, + ] + ) + except ValueError: + return True + + def keyword_matches(self, keyword: str) -> bool: + return keyword.lower() in self.description.lower() + + +class HTTPResponseException(Exception): + """ + An exception for when a HTTP response is not requests.codes.ok + """ + + def __init__(self, http_code: int, *args: object) -> None: + super().__init__(*args) + self.http_code = http_code + + +def _get_coupons(n: int, ignore_expiry: bool, keywords: list[str]) -> list[Coupon]: + """ + Returns a list of n Coupons + """ + + coupons = _get_coupons_from_page() + + if not ignore_expiry: + coupons = [coupon for coupon in coupons if coupon.is_valid()] + + if keywords: + coupons = [ + coupon + for coupon in coupons + if any(coupon.keyword_matches(keyword) for keyword in keywords) + ] + return coupons[:n] + + +def _get_coupons_from_page() -> list[Coupon]: + """ + Strips results from html page and returns a list of Coupon(s) + """ + http_response = requests.get(COUPONESE_DOMINOS_URL) + if http_response.status_code != requests.codes.ok: + raise HTTPResponseException(COUPONESE_DOMINOS_URL, http_response.status_code) + soup = BeautifulSoup(http_response.content, "html.parser") + soup_coupons = soup.find_all(class_="ov-coupon") + + coupons = [] + + for soup_coupon in soup_coupons: + expiry_date_str = soup_coupon.find(class_="ov-expiry").get_text(strip=True) + description = soup_coupon.find(class_="ov-desc").get_text(strip=True) + code = soup_coupon.find(class_="ov-code").get_text(strip=True) + coupon = Coupon(code, expiry_date_str, description) + coupons.append(coupon) + + return coupons + + +async def setup(bot: UQCSBot): + await bot.add_cog(DominosCoupons(bot)) From bd6a1dc37964f932275ba51c1836d8a622218213 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Fri, 30 Jun 2023 20:42:43 +1000 Subject: [PATCH 2/4] Dominos: Added embeds --- uqcsbot/dominos_coupons.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py index 6982094..6f09307 100644 --- a/uqcsbot/dominos_coupons.py +++ b/uqcsbot/dominos_coupons.py @@ -68,10 +68,20 @@ async def dominoscoupons( ) return + embed = discord.Embed( + title="Domino's Coupons", + url=COUPONESE_DOMINOS_URL, + description=f"Keywords: {keywords}", + timestamp=datetime.now(), + ) message = "Domino's Coupons:\n" for coupon in coupons: - message += f"`{coupon.code}` - {coupon.description} *[Expires: {coupon.expiry_date}]*\n" - await interaction.edit_original_response(content=message) + embed.add_field( + name=coupon.code, + value=f"{coupon.description} *[Expires: {coupon.expiry_date}]*", + inline=False, + ) + await interaction.edit_original_response(embed=embed) class Coupon: From 3827781641434d53642839daa8e1b0a07d8039bd Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Fri, 30 Jun 2023 20:50:46 +1000 Subject: [PATCH 3/4] Dominos: Fixed typing --- uqcsbot/dominos_coupons.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py index 6f09307..583251f 100644 --- a/uqcsbot/dominos_coupons.py +++ b/uqcsbot/dominos_coupons.py @@ -1,5 +1,5 @@ from datetime import datetime -from typing import Optional +from typing import List import logging import requests from requests.exceptions import RequestException @@ -29,9 +29,9 @@ def __init__(self, bot: UQCSBot): async def dominoscoupons( self, interaction: discord.Interaction, - number_of_coupons: Optional[int] = 5, - ignore_expiry: Optional[bool] = True, - keywords: Optional[str] = "", + number_of_coupons: int = 5, + ignore_expiry: bool = True, + keywords: str = "", ): """ Returns a list of dominos coupons @@ -55,7 +55,7 @@ async def dominoscoupons( return except HTTPResponseException as error: logging.warning( - f"Received a HTTP response code that was not OK (200), namely ({error.http_code}). Error information: {error.message}" + f"Received a HTTP response code that was not OK (200), namely ({error.http_code}). Error information: {error}" ) await interaction.edit_original_response( content=f"Could not find the coupons on the coupon website (<{COUPONESE_DOMINOS_URL}>)..." @@ -74,7 +74,6 @@ async def dominoscoupons( description=f"Keywords: {keywords}", timestamp=datetime.now(), ) - message = "Domino's Coupons:\n" for coupon in coupons: embed.add_field( name=coupon.code, @@ -118,7 +117,7 @@ def __init__(self, http_code: int, *args: object) -> None: self.http_code = http_code -def _get_coupons(n: int, ignore_expiry: bool, keywords: list[str]) -> list[Coupon]: +def _get_coupons(n: int, ignore_expiry: bool, keywords: List[str]) -> List[Coupon]: """ Returns a list of n Coupons """ @@ -137,17 +136,17 @@ def _get_coupons(n: int, ignore_expiry: bool, keywords: list[str]) -> list[Coupo return coupons[:n] -def _get_coupons_from_page() -> list[Coupon]: +def _get_coupons_from_page() -> List[Coupon]: """ Strips results from html page and returns a list of Coupon(s) """ http_response = requests.get(COUPONESE_DOMINOS_URL) if http_response.status_code != requests.codes.ok: - raise HTTPResponseException(COUPONESE_DOMINOS_URL, http_response.status_code) + raise HTTPResponseException(http_response.status_code) soup = BeautifulSoup(http_response.content, "html.parser") soup_coupons = soup.find_all(class_="ov-coupon") - coupons = [] + coupons: List[Coupon] = [] for soup_coupon in soup_coupons: expiry_date_str = soup_coupon.find(class_="ov-expiry").get_text(strip=True) From e9ad8e2e5f94d235e8361ae1bf721c3a135f7133 Mon Sep 17 00:00:00 2001 From: Isaac Beh Date: Sat, 1 Jul 2023 21:20:11 +1000 Subject: [PATCH 4/4] Dominos: Clarified error message --- uqcsbot/dominos_coupons.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/uqcsbot/dominos_coupons.py b/uqcsbot/dominos_coupons.py index 583251f..0553d94 100644 --- a/uqcsbot/dominos_coupons.py +++ b/uqcsbot/dominos_coupons.py @@ -16,6 +16,16 @@ COUPONESE_DOMINOS_URL = "https://www.couponese.com/store/dominos.com.au/" +class HTTPResponseException(Exception): + """ + An exception for when a HTTP response is not requests.codes.ok + """ + + def __init__(self, http_code: int, *args: object) -> None: + super().__init__(*args) + self.http_code = http_code + + class DominosCoupons(commands.Cog): def __init__(self, bot: UQCSBot): self.bot = bot @@ -55,7 +65,7 @@ async def dominoscoupons( return except HTTPResponseException as error: logging.warning( - f"Received a HTTP response code that was not OK (200), namely ({error.http_code}). Error information: {error}" + f"Received a HTTP response code {error.http_code}. Error information: {error}" ) await interaction.edit_original_response( content=f"Could not find the coupons on the coupon website (<{COUPONESE_DOMINOS_URL}>)..." @@ -107,16 +117,6 @@ def keyword_matches(self, keyword: str) -> bool: return keyword.lower() in self.description.lower() -class HTTPResponseException(Exception): - """ - An exception for when a HTTP response is not requests.codes.ok - """ - - def __init__(self, http_code: int, *args: object) -> None: - super().__init__(*args) - self.http_code = http_code - - def _get_coupons(n: int, ignore_expiry: bool, keywords: List[str]) -> List[Coupon]: """ Returns a list of n Coupons