Skip to content

Commit

Permalink
feat: version 3.25 (#88)
Browse files Browse the repository at this point in the history
* feat: version 3.25

* fix: push unhandled errors to logoo

* chore: ignore logoo debug logs

* feat: log on suggestion archive if channel is none

* fix: suggestion logging

* feat: log on setup failure

* feat: attach suggestion content on failure

* feat: support newlines in notes

* feat: move to public disnake release and remove bot-base dep

* chore: remove library_modifications.md as we no longer modify it within a fork

* fix: message editing missing required argument

* feat: add moderator notes to resolved suggestions

* fix: tests
  • Loading branch information
Skelmis authored Jun 13, 2024
1 parent 1f9cbad commit 1837738
Show file tree
Hide file tree
Showing 20 changed files with 536 additions and 192 deletions.
71 changes: 0 additions & 71 deletions library_modifications.md

This file was deleted.

3 changes: 2 additions & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from dotenv import load_dotenv
from logoo import PrimaryLogger


import suggestions

load_dotenv()
Expand All @@ -33,7 +34,7 @@
httpx_logger.setLevel(logging.WARNING)

logoo_logger = logging.getLogger("logoo")
logoo_logger.setLevel(logging.DEBUG)
logoo_logger.setLevel(logging.INFO)

suggestions_logger = logging.getLogger("suggestions")
suggestions_logger.setLevel(logging.DEBUG)
Expand Down
5 changes: 2 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@ alaric==1.2.0
anyio==4.2.0
async-timeout==4.0.2
attrs==21.4.0
Bot-Base==1.7.1
Brotli==1.0.9
causar==0.2.0
cchardet==2.1.7
certifi==2023.11.17
cffi==1.15.0
charset-normalizer==2.0.12
disnake @ git+https://github.com/suggestionsbot/disnake.git@22a572afd139144c0e2de49c7692a73ab74d8a3d
disnake==2.9.2
disnake-ext-components @ git+https://github.com/suggestionsbot/disnake-ext-components.git@91689ed74ffee73f631453a39e548af9b824826d
dnspython==2.2.1
exceptiongroup==1.2.0
Expand Down Expand Up @@ -43,7 +42,7 @@ pytest==7.1.3
pytest-asyncio==0.19.0
python-dotenv==0.20.0
sentinels==1.0.0
skelmis-commons==1.1.0
skelmis-commons==1.2.1
sniffio==1.3.0
tomli==2.0.1
typing_extensions==4.3.0
Expand Down
139 changes: 72 additions & 67 deletions suggestions/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,19 @@
import traceback
from pathlib import Path
from string import Template
from typing import Type, Optional, Union
from typing import Type, Optional, Union, Any

import aiohttp
import alaric
import commons
import disnake
import humanize
from alaric import Cursor
from bot_base.wraps import WrappedChannel
from cooldowns import CallableOnCooldown
from disnake import Locale, LocalizationKeyError
from disnake import Locale, LocalizationKeyError, Thread
from disnake.abc import PrivateChannel, GuildChannel
from disnake.ext import commands
from bot_base import BotBase, BotContext, PrefixNotFound
from disnake.state import AutoShardedConnectionState
from logoo import Logger

from suggestions import State, Colors, Emojis, ErrorCode, Garven
Expand All @@ -45,6 +46,7 @@
)
from suggestions.http_error_parser import try_parse_http_error
from suggestions.interaction_handler import InteractionHandler
from suggestions.low_level import PatchedConnectionState
from suggestions.objects import Error, GuildConfig, UserConfig
from suggestions.stats import Stats, StatsEnum
from suggestions.database import SuggestionsMongoManager
Expand All @@ -54,14 +56,17 @@
logger = Logger(__name__)


class SuggestionsBot(commands.AutoShardedInteractionBot, BotBase):
class SuggestionsBot(commands.AutoShardedInteractionBot):
def __init__(self, *args, **kwargs):
self.version: str = "Public Release 3.24"
self.version: str = "Public Release 3.25"
self.main_guild_id: int = 601219766258106399
self.legacy_beta_role_id: int = 995588041991274547
self.automated_beta_role_id: int = 998173237282361425
self.beta_channel_id: int = 995622792294830080
self.base_website_url: str = "https://suggestions.gg"
self._uptime: datetime.datetime = datetime.datetime.now(
tz=datetime.timezone.utc
)

self.is_prod: bool = True if os.environ.get("PROD", None) else False

Expand Down Expand Up @@ -107,8 +112,6 @@ def __init__(self, *args, **kwargs):
super().__init__(
*args,
**kwargs,
leave_db=True,
do_command_stats=False,
activity=disnake.Activity(
name="suggestions",
type=disnake.ActivityType.watching,
Expand All @@ -121,6 +124,19 @@ def __init__(self, *args, **kwargs):

self.zonis: ZonisRoutes = ZonisRoutes(self)

# This exists on the basis we have patched state
self.guild_ids: set[int] = self._connection.guild_ids

def _get_state(self, **options: Any) -> AutoShardedConnectionState:
return PatchedConnectionState(
**options,
dispatch=self.dispatch,
handlers=self._handlers,
hooks=self._hooks,
http=self.http,
loop=self.loop,
)

async def launch_shard(
self, _gateway: str, shard_id: int, *, initial: bool = False
) -> None:
Expand All @@ -134,9 +150,15 @@ async def before_identify_hook(
# gateway-proxy
return

async def get_or_fetch_channel(self, channel_id: int) -> WrappedChannel:
async def get_or_fetch_channel(
self, channel_id: int
) -> Union[GuildChannel, PrivateChannel, Thread]:
try:
return await super().get_or_fetch_channel(channel_id)
channel = self.get_channel(channel_id)
if channel is None:
channel = await self.fetch_channel(channel_id)

return channel
except disnake.NotFound as e:
raise ConfiguredChannelNoLongerExists from e

Expand All @@ -150,6 +172,17 @@ async def dispatch_initial_ready(self):
log.info("Startup took: %s", self.get_uptime())
await self.suggestion_emojis.populate_emojis()

@property
def uptime(self) -> datetime.datetime:
"""Returns when the bot was initialized."""
return self._uptime

def get_uptime(self) -> str:
"""Returns a human readable string for the bots uptime."""
return humanize.precisedelta(
self.uptime - datetime.datetime.now(tz=datetime.timezone.utc)
)

async def on_resumed(self):
if self.gc_lock.locked():
return
Expand Down Expand Up @@ -199,53 +232,6 @@ def error_embed(

return embed

async def process_commands(self, message: disnake.Message):
try:
prefix = await self.get_guild_prefix(message.guild.id)
prefix = self.get_case_insensitive_prefix(message.content, prefix)
except (AttributeError, PrefixNotFound):
prefix = self.get_case_insensitive_prefix(
message.content, self.DEFAULT_PREFIX
)

as_args: list[str] = message.content.split(" ")
command_to_invoke: str = as_args[0]
if not command_to_invoke.startswith(prefix):
# Not our prefix
return

command_to_invoke = command_to_invoke[len(prefix) :]

if command_to_invoke in self.old_prefixed_commands:
embed: disnake.Embed = disnake.Embed(
title="Maintenance mode",
description="Sadly this command is in maintenance mode.\n"
# "You can read more about how this affects you [here]()",
"You can read more about how this affects you by following our announcements channel.",
colour=disnake.Color.from_rgb(255, 148, 148),
)
return await message.channel.send(embed=embed)

elif command_to_invoke in self.converted_prefix_commands:
embed: disnake.Embed = disnake.Embed(
description="We are moving with the times, as such this command is now a slash command.\n"
"You can read more about how this affects you as well as ensuring you can "
# "use the bots commands [here]()",
"use the bots commands by following our announcements channel.",
colour=disnake.Color.magenta(),
)
return await message.channel.send(embed=embed)

ctx = await self.get_context(message, cls=BotContext)
if ctx.command:
log.debug(
"Attempting to invoke command %s for User(id=%s)",
ctx.command.qualified_name,
ctx.author.id,
)

await self.invoke(ctx)

async def _push_slash_error_stats(
self,
interaction: disnake.ApplicationCommandInteraction | disnake.MessageInteraction,
Expand Down Expand Up @@ -307,6 +293,15 @@ async def on_slash_command_error(
return

if isinstance(exception, UnhandledError):
logger.critical(
"An unhandled exception occurred",
extra_metadata={
"error_id": error.id,
"author_id": error.user_id,
"guild_id": error.guild_id,
"traceback": commons.exception_as_string(exception),
},
)
return await interaction.send(
embed=self.error_embed(
"Something went wrong",
Expand Down Expand Up @@ -464,11 +459,15 @@ async def on_slash_command_error(
return await interaction.send(
embed=self.error_embed(
"Command failed",
"Your suggestion content was too long, please limit it to 1000 characters or less.",
"Your suggestion content was too long, please limit it to 1000 characters or less.\n\n"
"I have attached a file containing your suggestion content to save rewriting it entirely.",
error_code=ErrorCode.SUGGESTION_CONTENT_TOO_LONG,
error=error,
),
ephemeral=True,
file=disnake.File(
io.StringIO(exception.suggestion_text), filename="suggestion.txt"
),
)

elif isinstance(exception, InvalidGuildConfigOption):
Expand Down Expand Up @@ -622,9 +621,7 @@ async def on_slash_command_error(
ih: InteractionHandler = await InteractionHandler.fetch_handler(
interaction.id, self
)
if interaction.deferred_without_send or (
ih is not None and not ih.has_sent_something
):
if ih is not None and not ih.has_sent_something:
gid = interaction.guild_id if interaction.guild_id else None
# Fix "Bot is thinking" hanging on edge cases...
await interaction.send(
Expand Down Expand Up @@ -717,14 +714,18 @@ async def process_watch_for_shutdown():
)
items = await cursor.execute()
if not items:
await self.sleep_with_condition(15, lambda: self.state.is_closing)
await commons.sleep_with_condition(
15, lambda: self.state.is_closing
)
continue

entry = items[0]
if not entry or (
entry and self.cluster_id in entry["responded_clusters"]
):
await self.sleep_with_condition(15, lambda: self.state.is_closing)
await commons.sleep_with_condition(
15, lambda: self.state.is_closing
)
continue

# We need to respond
Expand Down Expand Up @@ -766,7 +767,7 @@ async def process_update_bot_listings():
try:
total_guilds = await self.garven.get_total_guilds()
except PartialResponse:
await self.sleep_with_condition(
await commons.sleep_with_condition(
time_between_updates.total_seconds(),
lambda: self.state.is_closing,
)
Expand All @@ -787,7 +788,7 @@ async def process_update_bot_listings():
logger.warning("%s", r.text)

log.debug("Updated bot listings")
await self.sleep_with_condition(
await commons.sleep_with_condition(
time_between_updates.total_seconds(),
lambda: self.state.is_closing,
)
Expand Down Expand Up @@ -935,13 +936,17 @@ async def inner():
):
pass

await self.sleep_with_condition(60, lambda: self.state.is_closing)
await commons.sleep_with_condition(
60, lambda: self.state.is_closing
)
except (aiohttp.ClientConnectorError, ConnectionRefusedError):
log.warning("push_status failed to connect, retrying in 10 seconds")
logger.warning(
"push_status failed to connect, retrying in 10 seconds"
)
await self.sleep_with_condition(10, lambda: self.state.is_closing)
await commons.sleep_with_condition(
10, lambda: self.state.is_closing
)
except Exception as e:
if not self.is_prod:
log.error("Borked it")
Expand Down
2 changes: 1 addition & 1 deletion suggestions/cogs/suggestion_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ async def suggest(
anonymously: {{SUGGEST_ARG_ANONYMOUSLY}}
"""
if len(suggestion) > 1000:
raise SuggestionTooLong
raise SuggestionTooLong(suggestion)

await interaction.response.defer(ephemeral=True)

Expand Down
Loading

0 comments on commit 1837738

Please sign in to comment.