aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Muhammad Hasan <[email protected]>2025-11-10 05:47:35 +0500
committerGravatar GitHub <[email protected]>2025-11-10 00:47:35 +0000
commit26c3664978a21be8ba77d321b99c291f550da7e3 (patch)
tree264d9f47c896c1c2cae437ac9687404007a2fb57
parentDisable Space cog loading temporarily (diff)
Add .quote daily and .quote random command to retrieve quotes from zenquotes.io api (#1707)HEADmain
* feat: Add .quote and .daily_quote command to retrieve a random quote and the daily quote from the zenquotes.io api respectively * Implement caching for daily quote to minimize API requests. --------- Co-authored-by: Joe Banks <[email protected]>
-rw-r--r--bot/exts/fun/fun.py53
-rw-r--r--bot/utils/quote.py53
2 files changed, 106 insertions, 0 deletions
diff --git a/bot/exts/fun/fun.py b/bot/exts/fun/fun.py
index 66c48517..25c46413 100644
--- a/bot/exts/fun/fun.py
+++ b/bot/exts/fun/fun.py
@@ -5,6 +5,7 @@ from pathlib import Path
from typing import Literal
import pyjokes
+from aiohttp import ClientError, ClientResponseError
from discord import Embed
from discord.ext import commands
from discord.ext.commands import BadArgument, Cog, Context
@@ -14,6 +15,7 @@ from pydis_core.utils.logging import get_logger
from bot.bot import Bot
from bot.constants import Client, Colours, Emojis
from bot.utils import helpers, messages
+from bot.utils.quote import daily_quote, random_quote
log = get_logger(__name__)
@@ -158,6 +160,57 @@ class Fun(Cog):
joke = pyjokes.get_joke(category=category)
await ctx.send(joke)
+ @commands.group(name="quote")
+ async def quote(self, ctx: Context) -> None:
+ """
+ Retrieve a quote from zenquotes.io api.
+
+ See `random`, `daily` subcommands.
+ """
+ if ctx.invoked_subcommand is None:
+ await ctx.invoke(self.bot.get_command("help"), "quote")
+
+ @quote.command(name="daily")
+ async def quote_daily(self, ctx: Context) -> None:
+ """Retrieve the daily quote from zenquotes.io api."""
+ try:
+ quote = await daily_quote(self.bot)
+ embed = Embed(
+ title="Daily Quote",
+ description=f"> {quote}\n\n-# Powered by [zenquotes.io](https://zenquotes.io)",
+ colour=Colours.blue
+ )
+ await ctx.send(embed=embed)
+ except ClientResponseError as e:
+ log.warning(f"ZenQuotes API error: {e.status} {e.message}")
+ await ctx.send(":x: Could not retrieve quote from API.")
+ except (ClientError, TimeoutError) as e:
+ log.error(f"Network error fetching quote: {e}")
+ await ctx.send(":x: Could not connect to the quote service.")
+ except Exception:
+ log.exception("Unexpected error fetching quote.")
+ await ctx.send(":x: Something unexpected happened. Try again later.")
+
+ @quote.command(name="random")
+ async def quote_random(self, ctx: Context) -> None:
+ """Retrieve a random quote from zenquotes.io api."""
+ try:
+ quote = await random_quote(self.bot)
+ embed = Embed(
+ title="Random Quote",
+ description=f"> {quote}\n\n-# Powered by [zenquotes.io](https://zenquotes.io)",
+ colour=Colours.blue
+ )
+ await ctx.send(embed=embed)
+ except ClientResponseError as e:
+ log.warning(f"ZenQuotes API error: {e.status} {e.message}")
+ await ctx.send(":x: Could not retrieve quote from API.")
+ except (ClientError, TimeoutError) as e:
+ log.error(f"Network error fetching quote: {e}")
+ await ctx.send(":x: Could not connect to the quote service.")
+ except Exception:
+ log.exception("Unexpected error fetching quote.")
+ await ctx.send(":x: Something unexpected happened. Try again later.")
async def setup(bot: Bot) -> None:
"""Load the Fun cog."""
diff --git a/bot/utils/quote.py b/bot/utils/quote.py
new file mode 100644
index 00000000..9324a732
--- /dev/null
+++ b/bot/utils/quote.py
@@ -0,0 +1,53 @@
+"""Utility functions for fetching quotes from ZenQuotes API."""
+
+from datetime import UTC, datetime, timedelta
+
+from pydis_core.utils.logging import get_logger
+
+from bot.bot import Bot
+
+RANDOM_QUOTE_URL = "https://zenquotes.io/api/random"
+DAILY_QUOTE_URL = "https://zenquotes.io/api/today"
+DAILY_QUOTE_KEY="daily_quote"
+
+log = get_logger(__name__)
+
+def seconds_until_midnight_utc() -> int:
+ """Calculate the number of seconds remaining until midnight UTC for Redis cache TTL."""
+ now = datetime.now(UTC)
+ tomorrow = now + timedelta(days=1)
+ midnight = tomorrow.replace(hour=0, minute=0, second=0, microsecond=0)
+ time_to_midnight = (midnight - now)
+ return int(time_to_midnight.total_seconds())
+
+
+async def random_quote(bot: Bot) -> str:
+ """Retrieve a random quote from ZenQuotes API."""
+ async with bot.http_session.get(RANDOM_QUOTE_URL) as response:
+ response.raise_for_status()
+ data = await response.json()
+ quote = f"{data[0]['q']}\n*— {data[0]['a']}*"
+ return quote
+
+
+async def daily_quote(bot: Bot) -> str:
+ """Retrieve the daily quote from ZenQuotes API, cached until 00:00 UTC."""
+ redis = bot.redis_session.client
+
+ cached_quote = await redis.get(DAILY_QUOTE_KEY)
+ if cached_quote:
+ log.debug("Using cached daily quote.")
+ return cached_quote
+
+ log.debug("No cached quote found.")
+ async with bot.http_session.get(DAILY_QUOTE_URL) as resp:
+ resp.raise_for_status()
+ data = await resp.json()
+ quote = f"{data[0]['q']}\n*— {data[0]['a']}*"
+
+ ttl = seconds_until_midnight_utc()
+
+ await redis.set(DAILY_QUOTE_KEY, quote, ex=ttl)
+ log.info(f"Cached daily quote for {ttl} seconds.")
+
+ return quote