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
2 changes: 2 additions & 0 deletions techsupport_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -978,6 +978,8 @@ async def slash_command_log(self: Self, interaction: discord.Interaction) -> Non
Args:
interaction (discord.Interaction): The interaction the slash command generated
"""
if interaction.type != discord.InteractionType.application_command:
return
embed = discord.Embed()
embed.add_field(name="User", value=interaction.user)
embed.add_field(
Expand Down
118 changes: 88 additions & 30 deletions techsupport_bot/commands/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import discord
import munch
from botlogging import LogContext, LogLevel
from core import auxiliary, cogs, extensionconfig
from discord.ext import commands
from core import cogs, extensionconfig
from discord import app_commands

if TYPE_CHECKING:
import bot
Expand Down Expand Up @@ -108,13 +108,17 @@ async def preconfig(self: Self) -> None:
self.valid_category.append(item.value)

async def get_headlines(
self: Self, country_code: str, category: str = None
self: Self,
country_code: str,
category: str = None,
is_interaction: bool = False,
) -> list[munch.Munch]:
"""Calls the API to get the list of headlines based on the category and country

Args:
country_code (str): The country code to get headlines from
category (str, optional): The category of headlines to get. Defaults to None.
is_interaction (bool): If the headline is being called from an interaction

Returns:
list[munch.Munch]: The list of article objects from the API
Expand All @@ -126,27 +130,47 @@ async def get_headlines(
if category:
url = f"{url}&category={category}"

response = await self.bot.http_functions.http_call("get", url)
response = await self.bot.http_functions.http_call(
"get", url, use_app_error=is_interaction
)

articles = response.get("articles")
if not articles:
return None
return articles

async def get_random_headline(
self: Self, country_code: str, category: str = None
self: Self,
country_code: str,
category: str = None,
is_interaction: bool = False,
) -> munch.Munch:
"""Gets a single article object from the news API

Args:
country_code (str): The country code of the headliens to get
category (str, optional): The category of headlines to get. Defaults to None.
is_interaction (bool): If the headline is being called from an interaction

Returns:
munch.Munch: The raw API object representing a news headline
"""
articles = await self.get_headlines(country_code, category)
return random.choice(articles)

articles = await self.get_headlines(country_code, category, is_interaction)

# Filter out articles with URLs containing "removed.com"
filtered_articles = []
for article in articles:
url = article.get("url", "")
if url != "https://removed.com":
filtered_articles.append(article)

# Check if there are any articles left after filtering
if not filtered_articles:
return None

# Choose a random article from the filtered list
return random.choice(filtered_articles)

async def execute(self: Self, config: munch.Munch, guild: discord.Guild) -> None:
"""Loop entry point for the news command
Expand All @@ -168,6 +192,9 @@ async def execute(self: Self, config: munch.Munch, guild: discord.Guild) -> None
)
url = article.get("url")

if article is None:
return

log_channel = config.get("logging_channel")
await self.bot.logger.send_log(
message=f"Sending news headline to #{channel.name}",
Expand All @@ -187,48 +214,79 @@ async def wait(self: Self, config: munch.Munch, _: discord.Guild) -> None:
"""
await aiocron.crontab(config.extensions.news.cron_config.value).next()

@commands.group(
brief="Executes a news command",
description="Executes a news command",
)
async def news(self: Self, ctx: commands.Context) -> None:
"""The bare .news command. This does nothing but generate the help message

Args:
ctx (commands.Context): The context in which the command was run in
"""

# Executed if there are no/invalid args supplied
await auxiliary.extension_help(self, ctx, self.__module__[9:])

@news.command(
name="random",
brief="Gets a random news article",
@app_commands.command(
name="news",
description="Gets a random news headline",
usage="[category] (optional)",
extras={"module": "news"},
)
async def random(self: Self, ctx: commands.Context, category: str = None) -> None:
async def news_command(
self: Self, interaction: discord.Interaction, category: str = ""
) -> None:
"""Discord command entry point for getting a news article

Args:
ctx (commands.Context): The context in which the command was run
interaction (discord.Interaction): The interaction in which the command was run
category (str, optional): The category to get news headlines from. Defaults to None.
"""

# Debug statement
print("Executing news command")
if category is None or category.lower() not in self.valid_category:
category = random.choice(list(Category)).value
else:
category.lower()

config = self.bot.guild_configs[str(ctx.guild.id)]
config = self.bot.guild_configs[str(interaction.guild.id)]

url = None
while not url:
article = await self.get_random_headline(
config.extensions.news.country.value, category
config.extensions.news.country.value, category, True
)
url = article.get("url")

if article is None:
return

if url.endswith("/"):
url = url[:-1]

await ctx.send(content=url)
await interaction.response.send_message(content=url)

# Log the command execution
log_channel = config.get("logging_channel")
if log_channel:
await self.bot.logger.send_log(
message=(
f"News command executed: "
f"Sent a news headline to {interaction.channel.name}"
),
level=LogLevel.INFO,
context=LogContext(
guild=interaction.guild, channel=interaction.channel
),
channel=log_channel,
)

@news_command.autocomplete("category")
async def news_autocompletion(
self: Self, interaction: discord.Interaction, current: str
) -> list:
"""This command creates a list of categories for autocomplete the news command.

Args:
interaction (discord.Interaction): The interaction that started the command
current (str): The current input from the user.

Returns:
list: The list of autocomplete for the news command.
"""
# Debug statement
print("Autocomplete interaction")
news_category = []
for category in Category:
if current.lower() in category.value.lower():
news_category.append(
app_commands.Choice(name=category.value, value=category.value)
)
return news_category
15 changes: 15 additions & 0 deletions techsupport_bot/core/custom_errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ def __init__(self: Self, wait: int) -> None:
self.wait = wait


class HTTPRateLimitAppCommand(app_commands.CommandInvokeError):
"""An API call is on rate limit

Args:
wait (int): The amount of seconds left until the rate limit expires
"""

def __init__(self: Self, wait: int) -> None:
self.wait = wait


class ErrorResponse:
"""Object for generating a custom error message from an exception.

Expand Down Expand Up @@ -252,6 +263,10 @@ def get_message(self: Self, exception: Exception = None) -> str:
"That API is on cooldown. Try again in %.2f seconds",
{"key": "wait"},
),
HTTPRateLimitAppCommand: ErrorResponse(
"That API is on cooldown. Try again in %.2f seconds",
{"key": "wait"},
),
# -Custom errors-
FactoidNotFoundError: ErrorResponse(
"I couldn't find the factoid `%s`", {"key": "argument"}
Expand Down
4 changes: 4 additions & 0 deletions techsupport_bot/core/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,14 @@ async def http_call(

Raises:
HTTPRateLimit: Raised if the API is currently on cooldown
HTTPRateLimitAppCommand: Raised if the API is currently on cooldown

Returns:
munch.Munch: The munch object containing the response from the API
"""

# Get the URL not the endpoint being called
use_app_error = kwargs.pop("use_app_error", False)
ignore_rate_limit = False
root_url = urlparse(url).netloc

Expand Down Expand Up @@ -138,6 +140,8 @@ async def http_call(
now - self.url_rate_limit_history[root_url][0]
)
time_to_wait = max(time_to_wait, 0)
if use_app_error:
raise custom_errors.HTTPRateLimitAppCommand(time_to_wait)
raise custom_errors.HTTPRateLimit(time_to_wait)

# Add an entry for this call with the timestamp the call was placed
Expand Down
Loading