Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions bot/exts/fun/fun.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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
Expand All @@ -14,6 +15,7 @@
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__)

Expand Down Expand Up @@ -158,6 +160,57 @@ async def joke(self, ctx: commands.Context, category: Literal["neutral", "chuck"
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."""
Expand Down
53 changes: 53 additions & 0 deletions bot/utils/quote.py
Original file line number Diff line number Diff line change
@@ -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