Merge pull request #42 from UiO-Gaming/2024-q1-refactor

2024 q1 refactor
This commit is contained in:
Leander Furumo authored and GitHub committed 2024-04-05 02:43:16 +02:00
commit 9531797a53
40 files changed
+862 -1204

No files matched your search

+12
View File
@@ -62,6 +62,18 @@ All annen kode holdes på engelsk. Dette inkluderer kommentarer.
Et tips er å ha et engelsk funksjonsnavn, men spesifisere i decoratoren over med navnparameteren. Et tips er å ha et engelsk funksjonsnavn, men spesifisere i decoratoren over med navnparameteren.
### Bruk av embed templates
`error_warning` skal brukes om det er feil brukerinput, mangel på data eller generelt sett en feil som er forventet/ikke av utenforstående årsåker
`error_fatal` brukes når en uventet feil skjer. Ofte på grun av utenforstående årsaker som et API som er nede osv.
`success` kan brukes til å gi brukeren en bekreftelse på at operasjonen som ble forespurt gikk fint. Det vil si at den ikke skal brukes til å vise spesifikk data, men heller brukes når det ikke er noe å vise.
### Bruk av private variabler/metoder
Generelt sett unngår vi bruk av leading underscores for variabelnavn og metoder. Ja, de har sin bruk, men Python var aldri ment til å være objektorientert på samme måte som f.eks Java. Uansett skal en ikke bruke metoder fra andre cogs. Dermed er regelen: greit overalt utenom i cogs.
### Avhengigheter ### Avhengigheter
Hvis en ny avhengighet trengs, sørg for å spesifisere major og minor versjon i requirementsfila. Hvis en ny avhengighet trengs, sørg for å spesifisere major og minor versjon i requirementsfila.
+1 -1
View File
@@ -10,7 +10,7 @@ RUN apt-get update && apt-get install -y imagemagick ffmpeg libsm6 libxext6 grap
COPY . . COPY . .
RUN mv /app/src/assets/misc/policy.xml /etc/ImageMagick-6/policy.xml RUN mv /app/src/assets/policy.xml /etc/ImageMagick-6/policy.xml
VOLUME logs VOLUME logs
+1
View File
@@ -14,4 +14,5 @@ pypandoc==1.13.*
pyyaml==6.0.* pyyaml==6.0.*
regex==2023.12.25 regex==2023.12.25
requests==2.31.* requests==2.31.*
tzdata # This only needed if run on windows. I haven't set a version either to ensure it's up to date.
wordcloud==1.9.* wordcloud==1.9.*
File renamed without changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

File renamed without changes.
File renamed without changes.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

File renamed without changes.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

+60 -54
View File
@@ -12,12 +12,21 @@ from cogs.utils import embed_templates
class Anime(commands.Cog): class Anime(commands.Cog):
"""View information about different media on Anilist""" """View information about different media on Anilist"""
def __init__(self, bot: commands.Bot):
"""
Parameters
----------
bot (commands.Bot): The bot instance
"""
self.anilist_logo = "https://anilist.co/img/logo_al.png"
anilist = app_commands.Group(name="anilist", description="Hent informasjon fra Anilist") anilist = app_commands.Group(name="anilist", description="Hent informasjon fra Anilist")
anilist_profile = app_commands.Group( anilist_profile = app_commands.Group(
parent=anilist, name="profil", description="Hent informasjon om en bruker på Anilist" parent=anilist, name="profil", description="Hent informasjon om en bruker på Anilist"
) )
def __convert_color(self, color: str) -> int: def convert_color(self, color: str) -> int:
""" """
Converts anilist colors to hext Converts anilist colors to hext
@@ -39,10 +48,9 @@ class Anime(commands.Cog):
"green": 0x4CCA51, "green": 0x4CCA51,
"gray": 0x677B94, "gray": 0x677B94,
} }
return colors.get(color, 0x677B94) return colors.get(color, 0x677B94)
def __convert_media_format(self, media_format: str) -> str: def convert_media_format(self, media_format: str) -> str:
""" """
Translates media format names into norwegian Translates media format names into norwegian
@@ -67,7 +75,7 @@ class Anime(commands.Cog):
} }
return media_formats.get(media_format, media_format) return media_formats.get(media_format, media_format)
def __convert_language_names(self, language_name: str) -> str: def convert_language_names(self, language_name: str) -> str:
""" """
Translates language names into norwegian Translates language names into norwegian
@@ -92,10 +100,9 @@ class Anime(commands.Cog):
"HEBREW": "Hebraisk", "HEBREW": "Hebraisk",
"HUNGARIAN": "Ungarsk", "HUNGARIAN": "Ungarsk",
} }
return languages.get(language_name, language_name) return languages.get(language_name, language_name)
def __convert_role_names(self, role_name: str) -> str: def onvert_role_names(self, role_name: str) -> str:
""" """
Translates role names into norwegian Translates role names into norwegian
@@ -114,7 +121,7 @@ class Anime(commands.Cog):
except KeyError: except KeyError:
return role_name return role_name
def __convert_status(self, status: str) -> str: def convert_status(self, status: str) -> str:
""" """
Translates role names into norwegian Translates role names into norwegian
@@ -133,10 +140,9 @@ class Anime(commands.Cog):
"NOT_YET_RELEASED": "Ikke utgitt enda", "NOT_YET_RELEASED": "Ikke utgitt enda",
"CANCELLED": "Kansellert", "CANCELLED": "Kansellert",
} }
return statuses.get(status, status) return statuses.get(status, status)
def __remove_html(self, text: str) -> str: def remove_html(self, text: str) -> str:
""" """
Removes HTML tag elements Removes HTML tag elements
@@ -154,7 +160,7 @@ class Anime(commands.Cog):
return clean_text return clean_text
async def __request_anilist( async def request_anilist(
self, interaction: discord.Interaction, query: str, variables: dict, key: str self, interaction: discord.Interaction, query: str, variables: dict, key: str
) -> tuple[dict, str] | tuple[None, None]: ) -> tuple[dict, str] | tuple[None, None]:
""" """
@@ -179,13 +185,13 @@ class Anime(commands.Cog):
data = response["data"][key] data = response["data"][key]
url = data["siteUrl"] url = data["siteUrl"]
except TypeError: except TypeError:
embed = embed_templates.error_fatal(interaction, text="Kunne ikke finne det du søkte etter!") embed = embed_templates.error_fatal("Kunne ikke finne det du søkte etter!")
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
return None, None return None, None
return data, url return data, url
def __construct_favorite_media_string(self, media: list) -> str: def construct_favorite_media_string(self, media: list) -> str:
""" """
Constructs a string with the user's favorite media Constructs a string with the user's favorite media
@@ -210,7 +216,7 @@ class Anime(commands.Cog):
return "\n".join(favorite_media) return "\n".join(favorite_media)
def __construct_favorite_entity_string(self, entities: list, studio: bool = False) -> str: def construct_favorite_entity_string(self, entities: list, studio: bool = False) -> str:
""" """
Constructs a string with the user's favorite entities Constructs a string with the user's favorite entities
@@ -238,7 +244,7 @@ class Anime(commands.Cog):
return "\n".join(favortie_entities) return "\n".join(favortie_entities)
def __construct_release_schedule_string(self, numbers: dict) -> str: def construct_release_schedule_string(self, numbers: dict) -> str:
""" """
Constructs a string with the media's release schedule Constructs a string with the media's release schedule
@@ -358,7 +364,7 @@ class Anime(commands.Cog):
} }
""" """
variables = {"name": bruker} variables = {"name": bruker}
data, url = await self.__request_anilist(interaction, query, variables, "User") data, url = await self.request_anilist(interaction, query, variables, "User")
if not data: if not data:
return return
@@ -367,7 +373,7 @@ class Anime(commands.Cog):
profile_pic = data["avatar"]["large"] profile_pic = data["avatar"]["large"]
days_watched = round(data["statistics"]["anime"]["minutesWatched"] / 1440, 1) days_watched = round(data["statistics"]["anime"]["minutesWatched"] / 1440, 1)
chapters_read = data["statistics"]["manga"]["chaptersRead"] chapters_read = data["statistics"]["manga"]["chaptersRead"]
color = self.__convert_color(data["options"]["profileColor"]) color = self.convert_color(data["options"]["profileColor"])
anime_statuses = { anime_statuses = {
status["status"]: {"count": status["count"]} for status in data["statistics"]["anime"]["statuses"] status["status"]: {"count": status["count"]} for status in data["statistics"]["anime"]["statuses"]
@@ -385,14 +391,14 @@ class Anime(commands.Cog):
except KeyError: except KeyError:
manga_completed = 0 manga_completed = 0
favourite_anime = self.__construct_favorite_media_string(data["favourites"]["anime"]["nodes"]) favourite_anime = self.construct_favorite_media_string(data["favourites"]["anime"]["nodes"])
favourite_manga = self.__construct_favorite_media_string(data["favourites"]["manga"]["nodes"]) favourite_manga = self.construct_favorite_media_string(data["favourites"]["manga"]["nodes"])
favourite_character = self.__construct_favorite_entity_string(data["favourites"]["characters"]["nodes"]) favourite_character = self.construct_favorite_entity_string(data["favourites"]["characters"]["nodes"])
favourite_staff = self.__construct_favorite_entity_string(data["favourites"]["staff"]["nodes"]) favourite_staff = self.construct_favorite_entity_string(data["favourites"]["staff"]["nodes"])
favourite_studio = self.__construct_favorite_entity_string(data["favourites"]["studios"]["nodes"], studio=True) favourite_studio = self.construct_favorite_entity_string(data["favourites"]["studios"]["nodes"], studio=True)
embed = discord.Embed(title=user_name, color=color, url=url) embed = discord.Embed(title=user_name, color=color, url=url)
embed.set_author(name="Anilist", icon_url="https://anilist.co/img/logo_al.png") embed.set_author(name="Anilist", icon_url=self.anilist_logo)
embed.set_thumbnail(url=profile_pic) embed.set_thumbnail(url=profile_pic)
embed.add_field(name="Antall dager sett", value=days_watched) embed.add_field(name="Antall dager sett", value=days_watched)
embed.add_field(name="Antall anime sett", value=anime_completed) embed.add_field(name="Antall anime sett", value=anime_completed)
@@ -460,7 +466,7 @@ class Anime(commands.Cog):
} }
""" """
variables = {"name": bruker} variables = {"name": bruker}
data, url = await self.__request_anilist(interaction, query, variables, "User") data, url = await self.request_anilist(interaction, query, variables, "User")
if not data: if not data:
return return
@@ -469,7 +475,7 @@ class Anime(commands.Cog):
profile_pic = data["avatar"]["large"] profile_pic = data["avatar"]["large"]
days_watched = round(data["statistics"]["anime"]["minutesWatched"] / 1440, 1) days_watched = round(data["statistics"]["anime"]["minutesWatched"] / 1440, 1)
episodes_watched = data["statistics"]["anime"]["episodesWatched"] episodes_watched = data["statistics"]["anime"]["episodesWatched"]
color = self.__convert_color(data["options"]["profileColor"]) color = self.convert_color(data["options"]["profileColor"])
statuses = { statuses = {
status["status"]: {"count": status["count"], "minutes": status["minutesWatched"]} status["status"]: {"count": status["count"], "minutes": status["minutesWatched"]}
@@ -500,10 +506,10 @@ class Anime(commands.Cog):
anime_mean_score = "**Ingen**" anime_mean_score = "**Ingen**"
most_watched_genres = ", ".join([genre["genre"] for genre in data["statistics"]["anime"]["genres"]]) most_watched_genres = ", ".join([genre["genre"] for genre in data["statistics"]["anime"]["genres"]])
most_watched_studios = self.__construct_favorite_entity_string(data["statistics"]["anime"]["studios"]) most_watched_studios = self.construct_favorite_entity_string(data["statistics"]["anime"]["studios"])
embed = discord.Embed(title=user_name, color=color, url=url) embed = discord.Embed(title=user_name, color=color, url=url)
embed.set_author(name="Anilist", icon_url="https://anilist.co/img/logo_al.png") embed.set_author(name="Anilist", icon_url=self.anilist_logo)
embed.set_thumbnail(url=profile_pic) embed.set_thumbnail(url=profile_pic)
embed.add_field(name="Gj.snittsvurdering gitt", value=anime_mean_score) embed.add_field(name="Gj.snittsvurdering gitt", value=anime_mean_score)
embed.add_field(name="Antall dager sett", value=days_watched) embed.add_field(name="Antall dager sett", value=days_watched)
@@ -570,7 +576,7 @@ class Anime(commands.Cog):
} }
""" """
variables = {"name": bruker} variables = {"name": bruker}
data, url = await self.__request_anilist(interaction, query, variables, "User") data, url = await self.request_anilist(interaction, query, variables, "User")
if not data: if not data:
return return
@@ -579,7 +585,7 @@ class Anime(commands.Cog):
profile_pic = data["avatar"]["large"] profile_pic = data["avatar"]["large"]
chapters_read = round(data["statistics"]["manga"]["chaptersRead"] / 1440, 1) chapters_read = round(data["statistics"]["manga"]["chaptersRead"] / 1440, 1)
volumes_read = data["statistics"]["manga"]["volumesRead"] volumes_read = data["statistics"]["manga"]["volumesRead"]
color = self.__convert_color(data["options"]["profileColor"]) color = self.convert_color(data["options"]["profileColor"])
statuses = { statuses = {
status["status"]: {"count": status["count"], "minutes": status["chaptersRead"]} status["status"]: {"count": status["count"], "minutes": status["chaptersRead"]}
@@ -610,10 +616,10 @@ class Anime(commands.Cog):
manga_mean_score = "**Ingen**" manga_mean_score = "**Ingen**"
most_read_genres = ", ".join([genre["genre"] for genre in data["statistics"]["manga"]["genres"]]) most_read_genres = ", ".join([genre["genre"] for genre in data["statistics"]["manga"]["genres"]])
most_read_staff = self.__construct_favorite_entity_string(data["statistics"]["manga"]["staff"]) most_read_staff = self.construct_favorite_entity_string(data["statistics"]["manga"]["staff"])
embed = discord.Embed(title=user_name, color=color, url=url) embed = discord.Embed(title=user_name, color=color, url=url)
embed.set_author(name="Anilist", icon_url="https://anilist.co/img/logo_al.png") embed.set_author(name="Anilist", icon_url=self.anilist_logo)
embed.set_thumbnail(url=profile_pic) embed.set_thumbnail(url=profile_pic)
embed.add_field(name="Gj.snittsvurdering gitt", value=manga_mean_score) embed.add_field(name="Gj.snittsvurdering gitt", value=manga_mean_score)
embed.add_field(name="Antall kapitler lest", value=chapters_read) embed.add_field(name="Antall kapitler lest", value=chapters_read)
@@ -697,15 +703,15 @@ class Anime(commands.Cog):
} }
""" """
variables = {"search": navn, "isMain": True} variables = {"search": navn, "isMain": True}
data, url = await self.__request_anilist(interaction, query, variables, "Media") data, url = await self.request_anilist(interaction, query, variables, "Media")
if not data: if not data:
return return
nsfw = data["isAdult"] nsfw = data["isAdult"]
if nsfw: if nsfw:
embed = embed_templates.error_fatal( embed = embed_templates.error_warning(
interaction, text="Animen du søkte på er NSFW. " + "Gjør kommandoen i en NSFW-kanal i stedet" "Animen du søkte på er NSFW. Gjør kommandoen i en NSFW-kanal i stedet"
) )
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
@@ -713,7 +719,7 @@ class Anime(commands.Cog):
banner_image = data["bannerImage"] banner_image = data["bannerImage"]
mean_score = data["meanScore"] mean_score = data["meanScore"]
color = self.__convert_color(data["coverImage"]["color"]) color = self.convert_color(data["coverImage"]["color"])
title_romaji = data["title"]["romaji"] title_romaji = data["title"]["romaji"]
title_native = data["title"]["native"] title_native = data["title"]["native"]
@@ -721,9 +727,9 @@ class Anime(commands.Cog):
titles = [title_romaji, title_native, title_english] titles = [title_romaji, title_native, title_english]
title_romaji, title_native, title_english = [title if title else "" for title in titles] title_romaji, title_native, title_english = [title if title else "" for title in titles]
description = self.__remove_html(data["description"]) if data["description"] else "" description = self.remove_html(data["description"]) if data["description"] else ""
genres = ", ".join(data["genres"]) genres = ", ".join(data["genres"])
studios_string = self.__construct_favorite_entity_string(data["studios"]["nodes"], studio=True) studios_string = self.construct_favorite_entity_string(data["studios"]["nodes"], studio=True)
staff = data["staff"]["edges"] staff = data["staff"]["edges"]
director_string = "" director_string = ""
@@ -751,7 +757,7 @@ class Anime(commands.Cog):
"end_year": data["endDate"].get("year") if data["endDate"].get("year") else "?", "end_year": data["endDate"].get("year") if data["endDate"].get("year") else "?",
} }
date = self.__construct_release_schedule_string(numbers) date = self.construct_release_schedule_string(numbers)
length_string = "" length_string = ""
if numbers["duration"] == 1: if numbers["duration"] == 1:
@@ -765,11 +771,11 @@ class Anime(commands.Cog):
# Status # Status
status = data["status"] status = data["status"]
status = self.__convert_status(status) status = self.convert_status(status)
# Format # Format
media_format = data["format"] media_format = data["format"]
media_format = self.__convert_media_format(media_format) media_format = self.convert_media_format(media_format)
embed = discord.Embed(color=color, title=title_romaji, url=url, description=f"{title_native}\n{title_english}") embed = discord.Embed(color=color, title=title_romaji, url=url, description=f"{title_native}\n{title_english}")
embed.set_thumbnail(url=cover_image) embed.set_thumbnail(url=cover_image)
@@ -855,15 +861,15 @@ class Anime(commands.Cog):
} }
""" """
variables = {"search": navn} variables = {"search": navn}
data, url = await self.__request_anilist(interaction, query, variables, "Media") data, url = await self.request_anilist(interaction, query, variables, "Media")
if not data: if not data:
return return
nsfw = data["isAdult"] nsfw = data["isAdult"]
if nsfw: if nsfw:
embed = embed_templates.error_fatal( embed = embed_templates.error_warning(
interaction, text="Mangaen du søkte på er NSFW. " + "Gjør kommandoen i en NSFW-kanal i stedet" "Mangaen du søkte på er NSFW. Gjør kommandoen i en NSFW-kanal i stedet"
) )
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
@@ -871,7 +877,7 @@ class Anime(commands.Cog):
banner_image = data["bannerImage"] banner_image = data["bannerImage"]
mean_score = data["meanScore"] mean_score = data["meanScore"]
color = self.__convert_color(data["coverImage"]["color"]) color = self.convert_color(data["coverImage"]["color"])
title_romaji = data["title"]["romaji"] title_romaji = data["title"]["romaji"]
title_native = data["title"]["native"] title_native = data["title"]["native"]
@@ -879,7 +885,7 @@ class Anime(commands.Cog):
titles = [title_romaji, title_native, title_english] titles = [title_romaji, title_native, title_english]
title_romaji, title_native, title_english = [title if title else "" for title in titles] title_romaji, title_native, title_english = [title if title else "" for title in titles]
description = self.__remove_html(data["description"]) if data.get("description") else "" description = self.remove_html(data["description"]) if data.get("description") else ""
genres = ", ".join(data["genres"]) genres = ", ".join(data["genres"])
staff = data["staff"]["edges"] staff = data["staff"]["edges"]
@@ -901,7 +907,7 @@ class Anime(commands.Cog):
"end_month": data["endDate"].get("month") if data["endDate"].get("month") else "?", "end_month": data["endDate"].get("month") if data["endDate"].get("month") else "?",
"end_year": data["endDate"].get("year") if data["endDate"].get("year") else "?", "end_year": data["endDate"].get("year") if data["endDate"].get("year") else "?",
} }
date = self.__construct_release_schedule_string(numbers) date = self.construct_release_schedule_string(numbers)
chapters_string = "" chapters_string = ""
if numbers["chapters"] == 1: if numbers["chapters"] == 1:
@@ -910,10 +916,10 @@ class Anime(commands.Cog):
chapters_string += "kapitler" chapters_string += "kapitler"
status = data["status"] status = data["status"]
status = self.__convert_status(status) status = self.convert_status(status)
media_format = data["format"] media_format = data["format"]
media_format = self.__convert_media_format(media_format) media_format = self.convert_media_format(media_format)
embed = discord.Embed(color=color, title=title_romaji, url=url, description=f"{title_native}\n{title_english}") embed = discord.Embed(color=color, title=title_romaji, url=url, description=f"{title_native}\n{title_english}")
embed.set_thumbnail(url=cover_image) embed.set_thumbnail(url=cover_image)
@@ -982,7 +988,7 @@ class Anime(commands.Cog):
} }
""" """
variables = {"search": navn} variables = {"search": navn}
data, url = await self.__request_anilist(interaction, query, variables, "Character") data, url = await self.request_anilist(interaction, query, variables, "Character")
if not data: if not data:
return return
@@ -993,7 +999,7 @@ class Anime(commands.Cog):
favourites = data["favourites"] favourites = data["favourites"]
if data["description"]: if data["description"]:
description = self.__remove_html(data["description"]) description = self.remove_html(data["description"])
if len(description) > 1024: if len(description) > 1024:
description = description[0:1020] + "..." description = description[0:1020] + "..."
else: else:
@@ -1005,10 +1011,10 @@ class Anime(commands.Cog):
nsfwtag = "🔞" if media["node"]["isAdult"] else "" nsfwtag = "🔞" if media["node"]["isAdult"] else ""
media_name = media["node"]["title"]["romaji"] media_name = media["node"]["title"]["romaji"]
media_url = media["node"]["siteUrl"] media_url = media["node"]["siteUrl"]
media_role = self.__convert_role_names(media["characterRole"]) media_role = self.onvert_role_names(media["characterRole"])
for voice_actor in media["voiceActors"]: for voice_actor in media["voiceActors"]:
voice_actor_url = voice_actor["siteUrl"] voice_actor_url = voice_actor["siteUrl"]
voice_actor_language = self.__convert_language_names(voice_actor["language"]) voice_actor_language = self.convert_language_names(voice_actor["language"])
voice_actor_name = voice_actor["name"]["full"] voice_actor_name = voice_actor["name"]["full"]
if voice_actor["name"]["native"] != " " and voice_actor["name"]["native"] is not None: if voice_actor["name"]["native"] != " " and voice_actor["name"]["native"] is not None:
voice_actor_name += f' ({voice_actor["name"]["native"]}' voice_actor_name += f' ({voice_actor["name"]["native"]}'
@@ -1080,7 +1086,7 @@ class Anime(commands.Cog):
} }
""" """
variables = {"search": navn} variables = {"search": navn}
data, url = await self.__request_anilist(interaction, query, variables, "Staff") data, url = await self.request_anilist(interaction, query, variables, "Staff")
if not data: if not data:
return return
@@ -1091,7 +1097,7 @@ class Anime(commands.Cog):
favourites = data["favourites"] favourites = data["favourites"]
if data["description"]: if data["description"]:
description = self.__remove_html(data["description"]) description = self.remove_html(data["description"])
if len(description) > 1024: if len(description) > 1024:
description = description[0:1020] + "..." description = description[0:1020] + "..."
else: else:
@@ -1160,7 +1166,7 @@ class Anime(commands.Cog):
} }
""" """
variables = {"search": navn} variables = {"search": navn}
data, url = await self.__request_anilist(interaction, query, variables, "Studio") data, url = await self.request_anilist(interaction, query, variables, "Studio")
if not data: if not data:
return return
+4 -4
View File
@@ -21,7 +21,8 @@ class Aurora(commands.Cog):
self.bot = bot self.bot = bot
self.notified = datetime(2000, 9, 11) # Used to prevent spamming aurora alerts self.notified = datetime(2000, 9, 11) # Used to prevent spamming aurora alerts
# self.aurora_alarm.start() self.aurora_alarm.start()
self.AURORA_CHANNEL = 747542544291987599
async def get_forecast(self) -> dict | None: async def get_forecast(self) -> dict | None:
""" """
@@ -41,7 +42,6 @@ class Aurora(commands.Cog):
for interval in data["shortIntervals"]: for interval in data["shortIntervals"]:
# Convert to datetime objects, convert timezone to utc and strip timezone info # Convert to datetime objects, convert timezone to utc and strip timezone info
start = datetime.fromisoformat(interval["start"]).astimezone(pytz.utc).replace(tzinfo=None) start = datetime.fromisoformat(interval["start"]).astimezone(pytz.utc).replace(tzinfo=None)
# end = datetime.fromisoformat(interval["end"]).astimezone(pytz.utc).replace(tzinfo=None)
# If sighting is not in within the next 12 hours ignore # If sighting is not in within the next 12 hours ignore
if (start - datetime.now()) > timedelta(hours=12): if (start - datetime.now()) > timedelta(hours=12):
@@ -58,7 +58,7 @@ class Aurora(commands.Cog):
most_likely_sighting = max(valid_sightings, key=lambda x: x["auroraValue"]) most_likely_sighting = max(valid_sightings, key=lambda x: x["auroraValue"])
return most_likely_sighting return most_likely_sighting
@tasks.loop(minutes=10) @tasks.loop(minutes=60)
async def aurora_alarm(self): async def aurora_alarm(self):
""" """
Checks if the aurora forecast is above 50% and sends a message to the aurora channel if it is Checks if the aurora forecast is above 50% and sends a message to the aurora channel if it is
@@ -76,7 +76,7 @@ class Aurora(commands.Cog):
self.bot.logger.info("aurora_alarm: Fetch forecast") self.bot.logger.info("aurora_alarm: Fetch forecast")
return return
channel = self.bot.get_channel(747542544291987599) channel = self.bot.get_channel(self.AURORA_CHANNEL)
if not channel: if not channel:
self.bot.logger.warning("aurora_alarm: Failed to get aurora channel") self.bot.logger.warning("aurora_alarm: Failed to get aurora channel")
return return
+37 -43
View File
@@ -30,7 +30,9 @@ class Birthday(commands.Cog):
self.birthday_check.start() self.birthday_check.start()
def init_db(self): def init_db(self):
"""Create the necessary tables for the birthday cog to work""" """
Create the necessary tables for the birthday cog to work
"""
self.cursor.execute( self.cursor.execute(
""" """
@@ -45,9 +47,11 @@ class Birthday(commands.Cog):
name="bursdag", description="Se, endre eller fjern bursdager for brukere på serveren" name="bursdag", description="Se, endre eller fjern bursdager for brukere på serveren"
) )
@tasks.loop(hours=24) @tasks.loop(time=misc_utils.MIDNIGHT)
async def birthday_check(self): async def birthday_check(self):
"""Check if it's someone's birthday every day at midgnight and send a greeting if it is.""" """
Check if it's someone's birthday every day at midgnight and send a greeting if it is
"""
await asyncio.sleep(1) # Prevent sending message before date has really changed await asyncio.sleep(1) # Prevent sending message before date has really changed
@@ -64,27 +68,25 @@ class Birthday(commands.Cog):
birthdays = self.cursor.fetchall() birthdays = self.cursor.fetchall()
self.bot.logger.info(f"Found the following birthdays: {birthdays}") # TODO: Temporary. Remove self.bot.logger.info(f"Found the following birthdays: {birthdays}") # TODO: Temporary. Remove
if birthdays:
guild = self.bot.get_guild(747542543750660178)
channel = guild.get_channel(747542544291987597)
for birthday in birthdays:
user = await guild.fetch_member(birthday[0])
if user:
await channel.send(f"Gratulerer med dagen {user.mention}! 🥳")
else:
self.bot.logger.warning(f"Could not find user with ID {birthday[0]}")
@birthday_check.before_loop if not birthdays:
async def before_birthday_check(self): return
"""Syncs loop with the time of day"""
self.bot.logger.info("Postponing birthday check until midnight") guild = self.bot.get_guild(self.bot.UIO_GAMING_GUILD_ID)
await discord_utils.sleep_until_midnight(self.bot) channel = guild.get_channel(747542544291987597)
for birthday in birthdays:
user = await guild.fetch_member(birthday[0])
if user:
await channel.send(f"Gratulerer med dagen {user.mention}! 🥳")
else:
self.bot.logger.warning(f"Could not find user with ID {birthday[0]}")
def cog_unload(self): async def cog_unload(self):
self.bot.logger.info("Unloading cog")
self.birthday_check.cancel() self.birthday_check.cancel()
self.cursor.close()
def __fetch_user_birthday(self, user_id: int) -> datetime | None: def fetch_user_birthday(self, user_id: int) -> datetime | None:
""" """
Fetch the birthday of a user in the database Fetch the birthday of a user in the database
@@ -103,7 +105,7 @@ class Birthday(commands.Cog):
birthday = result[0] birthday = result[0]
return datetime(birthday.year, birthday.month, birthday.day) return datetime(birthday.year, birthday.month, birthday.day)
def __fetch_user_next_birthday(self, user_id: int) -> datetime: def fetch_user_next_birthday(self, user_id: int) -> datetime:
""" """
Get date of next birthday for a user Get date of next birthday for a user
@@ -132,7 +134,7 @@ class Birthday(commands.Cog):
# This will never happen unless a database error occurs. Keeping the type checker happy # This will never happen unless a database error occurs. Keeping the type checker happy
return datetime.now() return datetime.now()
def __fetch_next_birthdays(self) -> list[tuple[int, datetime, datetime]]: def fetch_next_birthdays(self) -> list[tuple[int, datetime, datetime]]:
""" """
Fetch the 5 first future birthdays of all users in the database Fetch the 5 first future birthdays of all users in the database
@@ -160,7 +162,7 @@ class Birthday(commands.Cog):
return birthdays return birthdays
def __set_user_birthday(self, user_id: int, birthday: datetime): def set_user_birthday(self, user_id: int, birthday: datetime):
""" """
Set the birthday of a user in the database. If it already exists, update it. Set the birthday of a user in the database. If it already exists, update it.
@@ -201,28 +203,24 @@ class Birthday(commands.Cog):
date = datetime(år, måned, dag) date = datetime(år, måned, dag)
except ValueError: except ValueError:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal(interaction, text="ikke en gyldig dato, gjøk!"), ephemeral=True embed=embed_templates.error_warning("ikke en gyldig dato, gjøk!"), ephemeral=True
) )
if date.year < 1970: if date.year < 1970:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal( embed=embed_templates.error_warning("Ok boomer, men nei. Hvorfor? Unix timestamps. Google it"),
interaction, text="Ok boomer, men nei. Hvorfor? Unix timestamps. Google it"
),
ephemeral=True, ephemeral=True,
) )
if date > datetime.now(): if date > datetime.now():
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal(interaction, text="Du kan ikke sette en fremtidig dato!"), embed=embed_templates.error_warning("Du kan ikke sette en fremtidig dato!"),
ephemeral=True, ephemeral=True,
) )
self.__set_user_birthday(interaction.user.id, date) self.set_user_birthday(interaction.user.id, date)
embed = embed_templates.success( embed = embed_templates.success(f"Bursdag satt til {discord.utils.format_dt(date, style='D')}")
interaction, text=f'Bursdag satt til {discord.utils.format_dt(date, style="D")}'
)
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True)
@@ -239,7 +237,7 @@ class Birthday(commands.Cog):
self.cursor.execute("DELETE FROM birthdays WHERE discord_id = (%s)", (interaction.user.id,)) self.cursor.execute("DELETE FROM birthdays WHERE discord_id = (%s)", (interaction.user.id,))
embed = embed_templates.success(interaction, text="Bursdag fjernet") embed = embed_templates.success("Bursdag fjernet")
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True)
@@ -260,11 +258,11 @@ class Birthday(commands.Cog):
if not bruker: if not bruker:
bruker = interaction.user bruker = interaction.user
user = self.__fetch_user_birthday(bruker.id) user = self.fetch_user_birthday(bruker.id)
if not user: if not user:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal(interaction, text="Brukeren har ikke registrert bursdagen sin") embed=embed_templates.error_warning("Brukeren har ikke registrert bursdagen sin")
) )
# Current age information # Current age information
@@ -273,10 +271,10 @@ class Birthday(commands.Cog):
years_old = relativedelta(datetime.now(), birthday).years years_old = relativedelta(datetime.now(), birthday).years
# Next birthday information # Next birthday information
next_birthday = self.__fetch_user_next_birthday(bruker.id) next_birthday = self.fetch_user_next_birthday(bruker.id)
next_birthday_days = (next_birthday - datetime.now()).days next_birthday_days = (next_birthday - datetime.now()).days
embed = discord.Embed(description=bruker.mention, color=discord_utils.get_color(bruker)) embed = discord.Embed(description=bruker.mention, color=bruker.color)
embed.set_thumbnail(url=bruker.display_avatar) embed.set_thumbnail(url=bruker.display_avatar)
embed.set_author(name=bruker.name, icon_url=bruker.display_avatar) embed.set_author(name=bruker.name, icon_url=bruker.display_avatar)
embed.add_field(name="Bursdag", value=discord.utils.format_dt(birthday, style="D")) embed.add_field(name="Bursdag", value=discord.utils.format_dt(birthday, style="D"))
@@ -303,12 +301,10 @@ class Birthday(commands.Cog):
await interaction.response.defer() await interaction.response.defer()
next_birthdays = self.__fetch_next_birthdays() next_birthdays = self.fetch_next_birthdays()
if not next_birthdays: if not next_birthdays:
return await interaction.followup.send( return await interaction.followup.send(embed=embed_templates.error_warning("Ingen bursdager"))
embed=embed_templates.error_fatal(interaction, text="Ingen bursdager")
)
birthday_strings = [] birthday_strings = []
for user in next_birthdays: for user in next_birthdays:
@@ -325,9 +321,7 @@ class Birthday(commands.Cog):
birthday_strings.append(f"* {discord_user.name} - {timestamp} ({relative_timestamp})") birthday_strings.append(f"* {discord_user.name} - {timestamp} ({relative_timestamp})")
if not birthday_strings: if not birthday_strings:
return await interaction.followup.send( return await interaction.followup.send(embed=embed_templates.error_warning("Ingen bursdager"))
embed=embed_templates.error_fatal(interaction, text="Ingen bursdager")
)
paginator = misc_utils.Paginator(birthday_strings) paginator = misc_utils.Paginator(birthday_strings)
view = discord_utils.Scroller(paginator, interaction.user) view = discord_utils.Scroller(paginator, interaction.user)
+6 -6
View File
@@ -71,8 +71,8 @@ class BotInfo(commands.Cog):
embed.set_author(name=dev.name, icon_url=dev.display_avatar) embed.set_author(name=dev.name, icon_url=dev.display_avatar)
embed.set_thumbnail(url=self.bot.user.display_avatar) embed.set_thumbnail(url=self.bot.user.display_avatar)
embed.add_field(name="Dev", value=f"{dev.mention}\n{dev.display_name}") embed.add_field(name="Dev", value=f"{dev.mention}\n{dev.display_name}")
embed.add_field(name="Oppetid", value=self.__get_uptime()) embed.add_field(name="Oppetid", value=self.get_uptime())
embed.add_field(name="Ping", value=f"Websocket ping: {self.__get_ping()} ms") embed.add_field(name="Ping", value=f"Websocket ping: {self.get_ping()} ms")
embed.add_field(name="Servere", value=len(self.bot.guilds)) embed.add_field(name="Servere", value=len(self.bot.guilds))
embed.add_field(name="Discord.py", value=discord.__version__) embed.add_field(name="Discord.py", value=discord.__version__)
embed.add_field(name="Python", value=platform.python_version()) embed.add_field(name="Python", value=platform.python_version())
@@ -106,7 +106,7 @@ class BotInfo(commands.Cog):
""" """
embed = discord.Embed(color=interaction.client.user.color) embed = discord.Embed(color=interaction.client.user.color)
embed.add_field(name="🔌 Oppetid", value=self.__get_uptime()) embed.add_field(name="🔌 Oppetid", value=self.get_uptime())
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True)
@@ -122,10 +122,10 @@ class BotInfo(commands.Cog):
""" """
embed = discord.Embed(color=interaction.client.user.color) embed = discord.Embed(color=interaction.client.user.color)
embed.add_field(name="📶 Ping", value=f"* **Websocket ping:** {self.__get_ping()} ms\n* **I hodet:** ∞") embed.add_field(name="📶 Ping", value=f"* **Websocket ping:** {self.get_ping()} ms\n* **I hodet:** ∞")
await interaction.response.send_message(embed=embed, content=None) await interaction.response.send_message(embed=embed, content=None)
def __get_ping(self) -> int: def get_ping(self) -> int:
""" """
Get the bot's ping in milliseconds Get the bot's ping in milliseconds
@@ -136,7 +136,7 @@ class BotInfo(commands.Cog):
return int(self.bot.latency * 1000) return int(self.bot.latency * 1000)
def __get_uptime(self) -> str: def get_uptime(self) -> str:
""" """
Returns the current uptime of the bot in string format Returns the current uptime of the bot in string format
+9 -91
View File
@@ -1,7 +1,6 @@
import asyncio import asyncio
import json import json
import random import random
import textwrap
from datetime import datetime from datetime import datetime
from datetime import timedelta from datetime import timedelta
from typing import override from typing import override
@@ -11,12 +10,9 @@ import discord
import numpy as np import numpy as np
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from cogs.utils import discord_utils
from cogs.utils import embed_templates from cogs.utils import embed_templates
from cogs.utils import misc_utils
from cogs.utils.discord_utils import Lobby from cogs.utils.discord_utils import Lobby
from cogs.utils.discord_utils import LobbyView from cogs.utils.discord_utils import LobbyView
@@ -53,19 +49,13 @@ class CS2Bingo(commands.Cog):
self.active_lobbies.pop(str(interaction.user.id)) self.active_lobbies.pop(str(interaction.user.id))
else: else:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning( embed=embed_templates.error_warning("Du hoster allerede en lobby")
interaction,
text="Du hoster allerede en lobby",
)
) )
for lobby in self.active_lobbies.values(): for lobby in self.active_lobbies.values():
if interaction.user.id in lobby.players: if interaction.user.id in lobby.players:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning( embed=embed_templates.error_warning("Du er allerede i en lobby")
interaction,
text="Du er allerede i en lobby",
)
) )
self.active_lobbies[str(interaction.user.id)] = Lobby( self.active_lobbies[str(interaction.user.id)] = Lobby(
@@ -80,7 +70,7 @@ class CS2Bingo(commands.Cog):
embed = discord.Embed( embed = discord.Embed(
title="Bingolobby", title="Bingolobby",
description=f"Lobbyen stenger {time_left}. Bli med innen da!", description=f"Lobbyen stenger {time_left}. Bli med innen da!",
color=discord_utils.get_color(interaction.user), color=interaction.user.color,
) )
embed.set_author(name=interaction.user.global_name, icon_url=interaction.user.avatar) embed.set_author(name=interaction.user.global_name, icon_url=interaction.user.avatar)
embed.add_field(name="Spillere", value=f"* {interaction.user.mention}", inline=False) embed.add_field(name="Spillere", value=f"* {interaction.user.mention}", inline=False)
@@ -103,77 +93,6 @@ class BingoGenerator:
with open(f"{BINGO_PATH}/names.json") as f: with open(f"{BINGO_PATH}/names.json") as f:
NAMES = json.load(f) NAMES = json.load(f)
@classmethod
async def put_text_in_box(
cls,
image: np.ndarray,
text: str,
top_left: tuple,
bottom_right: tuple,
font_path: str,
font_size: int = 10,
color: tuple = (0, 0, 0),
):
"""
Puts text inside a bounding box on the cv2 image with automatic text wrapping, using PIL for text rendering.
:param image: Source cv2 image (numpy array).
:param text: Text string to be put.
:param top_left: Tuple (x, y) defining the top-left corner of the bounding box.
:param bottom_right: Tuple (x, y) defining the bottom-right corner of the bounding box.
:param font_path: Path to a .ttf font file.
:param font_size: Initial font size.
:param color: Text color in RGB.
:return: Image with text (numpy array).
"""
# TODO: Dry. This algorithm is also seen in the memes cog
# Convert the cv2 image to a PIL image
image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(image_pil)
# Get coordinates of the bounding box
x1, y1 = top_left
x2, y2 = bottom_right
# Get the width and height of the bounding box
# We multiply by 0.9 to give some padding
w, h = (x2 - x1) * 0.9, (y2 - y1) * 0.9
# Initialize font width/height to 0 in order to run the while loops below at least once
font_width = 0
font_height = 0
# Add newlines to the text. Here we have gone with 15 characters per line
# This is arbitrary, but seems to work well
text = textwrap.fill(text, width=15)
# Increase the font size until the longest line of text does not fit in the bounding box anymore
# We also have to check the height, since the text might be too tall for the box
while font_width < w and font_height < h:
font = ImageFont.truetype(font_path, font_size)
longest_line = max(text.split("\n"), key=lambda x: font.getlength(x))
font_width = font.getlength(longest_line)
# bbox doesn't work with multiline text, so we have to calculate the height manually
# take the height of the bounding box, multiply by 1.5 (our assumed line dividor height)
# after that, we multiply by the number of lines
font_box = font.getbbox(text)
font_height = ((font_box[3] - font_box[1]) * 1.5) * text.count("\n")
font_size += 1
# Actually draw the text
draw.multiline_textbbox((w, h), text=text, font=font, align="center", anchor="mm")
center_of_box = ((x1 + x2) // 2, (y1 + y2) // 2)
draw.multiline_text(center_of_box, text, font=font, fill=color, align="center", anchor="mm")
# Convert back to cv2 image format and update the original image
cv2_image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
np.copyto(image, cv2_image)
@classmethod @classmethod
async def sample_cells(cls, players: list[str], bot: commands.Bot) -> dict[str, list[str]]: async def sample_cells(cls, players: list[str], bot: commands.Bot) -> dict[str, list[str]]:
assert 0 < len(players) <= 10 assert 0 < len(players) <= 10
@@ -238,11 +157,11 @@ class BingoGenerator:
for i in range(0, 5): for i in range(0, 5):
for j in range(0, 5): for j in range(0, 5):
width = 123 width = 123
await cls.put_text_in_box( await misc_utils.put_text_in_box(
sliced, sliced,
sample_space.pop(), sample_space.pop(),
(i * width + 1, j * width + 1), top_left=(i * width + 1, j * width + 1),
((i + 1) * width, (j + 1) * width), bottom_right=((i + 1) * width, (j + 1) * width),
font_path=cls.FONT_PATH, font_path=cls.FONT_PATH,
) )
@@ -257,7 +176,7 @@ class BingoView(LobbyView):
@override @override
async def start_lobby(self, interaction: discord.Interaction, button: discord.ui.Button): async def start_lobby(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user != self.lobby.host: if interaction.user != self.lobby.host:
embed = embed_templates.error_warning(interaction, text="Bare hosten kan starte bingoen") embed = embed_templates.error_warning("Bare hosten kan starte bingoen")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
await self.end_lobby() await self.end_lobby()
@@ -268,8 +187,7 @@ class BingoView(LobbyView):
mention_players = " ".join([f"{p.mention}" for p in self.lobby.players]) mention_players = " ".join([f"{p.mention}" for p in self.lobby.players])
embed = embed_templates.success( embed = embed_templates.success(
interaction, "Bingoen har startet\n\nDere vil nå få tilsendt bingobrettene deres på DM. "
text="Bingoen har startet\n\nDere vil nå få tilsendt bingobrettene deres på DM. "
+ "Sørg for at jeg kan slide inn i dem :smirk:", + "Sørg for at jeg kan slide inn i dem :smirk:",
) )
await interaction.response.send_message(content=mention_players, embed=embed) await interaction.response.send_message(content=mention_players, embed=embed)
-26
View File
@@ -1,26 +0,0 @@
import asyncio
from discord.ext import commands
class CameraChannel(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
async def on_voice_state_update(self, member, before, after):
if after.channel is None:
return
if member.voice.channel.id != 817125310491131949:
return
if not member.voice.self_video:
await asyncio.sleep(10)
if not member.voice.self_video and member.voice.channel.id == 817125310491131949:
await member.move_to(channel=None)
async def setup(bot: commands.Bot):
# bot.add_listener(CameraChannel(bot).on_voice_state_update, 'on_voice_state_update')
# await bot.add_cog(CameraChannel(bot))
pass
-45
View File
@@ -1,45 +0,0 @@
import json
import aiocron
import discord
from discord.ext import commands
from yr.libyr import Yr
class Vær(commands.Cog):
def __init__(self, bot: commands.Bot):
self.bot = bot
@aiocron.crontab("0 6 * * *")
async def forecast():
dev = await self.bot.fetch_user(162337748781367296)
guild = self.bot.get_guild(747542543750660178)
channel = guild.get_channel(750052141346979850)
weather = Yr(location_name="Norway/Oslo/Oslo/Oslo", forecast_link="forecast_hour_by_hour")
embed = discord.Embed(title=":white_sun_small_cloud: Værmelding for Oslo :white_sun_small_cloud:")
embed.description = "*Værmeldingen levert til deg av Petter*"
embed.set_author(name=dev.name, icon_url=dev.display_avatar)
half_hour = False
klokkeslett = 0
for forecast in weather.forecast(str):
if half_hour:
half_hour = False
continue
data = json.loads(forecast)
temp = data["temperature"]
rain = data["precipitation"]
regn = rain["@value"]
temperatur = temp["@value"]
embed.add_field(name=f"kl {klokkeslett}", value=f"{temperatur}°C - {regn}mm")
klokkeslett += 1
half_hour = True
await channel.send("RISE AND SHINE GAMERS!", embed=embed)
async def setup(bot: commands.Bot):
await bot.add_cog(Vær(bot))
-27
View File
@@ -1,27 +0,0 @@
import discord
import wikipedia
from discord.ext import commands
from cogs.utils import embed_templates
class Wiki(commands.Cog):
@commands.command()
async def wiki_search(self, ctx, *search_term):
search_term = " ".join(search_term)
search = wikipedia.search(search_term)
search = search[0]
search = wikipedia.page(search)
summary = wikipedia.summary(search_term, sentences=5)
url = search.url
embed = discord.Embed(title=f"Wikipediasøk for {search_term}", url=url)
embed.add_field(name=search_term, value=summary)
embed_templates.default_footer(ctx, embed)
await ctx.send(embed=embed)
async def setup(bot: commands.Bot):
await bot.add_cog(Wiki(bot))
+34 -28
View File
@@ -42,7 +42,7 @@ class DevTools(commands.Cog):
name="custommsg", name="custommsg",
description="Send en melding til hvilken som helst kanal på hvilken som helst server botten er i", description="Send en melding til hvilken som helst kanal på hvilken som helst server botten er i",
) )
async def custommsg(self, ctx: commands.Context, channel: int, *text: tuple[str]): async def custommsg(self, ctx: commands.Context, channel: int, text: str):
""" """
Send a message to any channel on any server the bot is in Send a message to any channel on any server the bot is in
@@ -50,17 +50,16 @@ class DevTools(commands.Cog):
---------- ----------
ctx (commands.Context): Context object ctx (commands.Context): Context object
channel (int): The channel ID to send the message to channel (int): The channel ID to send the message to
text (tuple[str]): The text to send text (str): The text to send
""" """
# Send message to the requested channel # Send message to the requested channel
channel = self.bot.get_channel(channel) channel = self.bot.get_channel(channel)
custommessage = " ".join(text) await channel.send(text)
await channel.send(custommessage)
# Send confirmation message to the invoker # Send confirmation message to the invoker
embed = discord.Embed(color=ctx.me.color) embed = discord.Embed(color=ctx.me.color)
embed.add_field(name="Sent", value=custommessage) embed.add_field(name="Sent", value=text)
await ctx.reply(embed=embed) await ctx.reply(embed=embed)
@commands.is_owner() @commands.is_owner()
@@ -115,7 +114,7 @@ class DevTools(commands.Cog):
try: try:
guild = await self.bot.fetch_guild(guild_id) guild = await self.bot.fetch_guild(guild_id)
except discord.errors.Forbidden: except discord.errors.Forbidden:
embed = embed_templates.error_fatal(ctx, text="Bot is not a member of this guild") embed = embed_templates.error_warning("Bot is not a member of this guild")
return await ctx.reply(embed=embed) return await ctx.reply(embed=embed)
# Send confirmation message for leaving # Send confirmation message for leaving
@@ -187,14 +186,16 @@ class DevTools(commands.Cog):
""" """
for file in listdir("./src/cogs"): for file in listdir("./src/cogs"):
if file.endswith(".py"): if not file.endswith(".py"):
name = file[:-3] continue
if name == cog:
await self.bot.unload_extension(f"cogs.{name}")
embed = discord.Embed(color=ctx.me.color, description=f"{cog} has been disabled")
return await ctx.reply(embed=embed)
embed = embed_templates.error_fatal(ctx, text=f"{cog} does not exist") name = file[:-3]
if name == cog:
await self.bot.unload_extension(f"cogs.{name}")
embed = discord.Embed(color=ctx.me.color, description=f"{cog} has been disabled")
return await ctx.reply(embed=embed)
embed = embed_templates.error_warning(f"{cog} does not exist")
await ctx.reply(embed=embed) await ctx.reply(embed=embed)
@cogs.command(name="load", description="Aktiver en cog") @cogs.command(name="load", description="Aktiver en cog")
@@ -209,14 +210,16 @@ class DevTools(commands.Cog):
""" """
for file in listdir("./src/cogs"): for file in listdir("./src/cogs"):
if file.endswith(".py"): if not file.endswith(".py"):
name = file[:-3] continue
if name == cog:
await self.bot.load_extension(f"cogs.{name}")
embed = discord.Embed(color=ctx.me.color, description=f"{cog} loaded")
return await ctx.reply(embed=embed)
embed = embed_templates.error_fatal(ctx, text=f"{cog} does not exist") name = file[:-3]
if name == cog:
await self.bot.load_extension(f"cogs.{name}")
embed = discord.Embed(color=ctx.me.color, description=f"{cog} loaded")
return await ctx.reply(embed=embed)
embed = embed_templates.error_warning(f"{cog} does not exist")
await ctx.reply(embed=embed) await ctx.reply(embed=embed)
@cogs.command(name="reload", description="Last inn en cog på nytt") @cogs.command(name="reload", description="Last inn en cog på nytt")
@@ -229,15 +232,18 @@ class DevTools(commands.Cog):
ctx (commands.Context): Context object ctx (commands.Context): Context object
cog (str): The name of the cog to reload cog (str): The name of the cog to reload
""" """
for file in listdir("./src/cogs"):
if file.endswith(".py"):
name = file[:-3]
if name == cog:
await self.bot.reload_extension(f"cogs.{name}")
embed = discord.Embed(color=ctx.me.color, description=f"{cog} has been reloaded")
return await ctx.reply(embed=embed)
embed = embed_templates.error_fatal(ctx, text=f"{cog} does not exist") for file in listdir("./src/cogs"):
if not file.endswith(".py"):
continue
name = file[:-3]
if name == cog:
await self.bot.reload_extension(f"cogs.{name}")
embed = discord.Embed(color=ctx.me.color, description=f"{cog} has been reloaded")
return await ctx.reply(embed=embed)
embed = embed_templates.error_warning(f"{cog} does not exist")
await ctx.reply(embed=embed) await ctx.reply(embed=embed)
@cogs.command(name="reloadunloaded", description="Last inn alle cogs på nytt, selv de som ikke er lastet inn") @cogs.command(name="reloadunloaded", description="Last inn alle cogs på nytt, selv de som ikke er lastet inn")
+69 -54
View File
@@ -76,50 +76,50 @@ class Errors(commands.Cog):
if hasattr(ctx.command, "on_error"): if hasattr(ctx.command, "on_error"):
return return
# Ignored errors
ignored = commands.CommandNotFound
error = getattr(error, "original", error) error = getattr(error, "original", error)
if isinstance(error, ignored):
# Ignored errors
ignored_errors = (commands.CommandNotFound, commands.DisabledCommand, commands.CheckFailure)
if isinstance(error, ignored_errors):
self.bot.logger.info("Ignored error", error)
return return
# Errors that should prompt a help message
send_help = (commands.MissingRequiredArgument, commands.TooManyArguments, commands.BadArgument) send_help = (commands.MissingRequiredArgument, commands.TooManyArguments, commands.BadArgument)
if isinstance(error, send_help): if isinstance(error, send_help):
self.bot.get_command(f"{ctx.command}").reset_cooldown(ctx)
return await ctx.send_help(ctx.command) return await ctx.send_help(ctx.command)
elif isinstance(error, commands.BotMissingPermissions): elif isinstance(error, commands.BotMissingPermissions):
permissions = ", ".join(error.missing_permissions) embed = self.error_missing_perms(error.missing_permissions, "Jeg")
text = "Jeg mangler følgende tillatelser:\n\n" + f"```\n{permissions}\n```"
return await ctx.reply(text)
elif isinstance(error, commands.MissingPermissions): elif isinstance(error, commands.MissingPermissions):
permissions = ", ".join(error.missing_permissions) embed = self.error_missing_perms(error.missing_permissions, "Du")
text = "Du mangler følgende tillatelser\n\n" + f"```\n{permissions}\n```"
return await ctx.reply(text)
elif isinstance(error, commands.NotOwner): elif isinstance(error, commands.NotOwner):
text = "Bare boteieren kan gjøre dette" embed = embed_templates.error_warning("Bare boteieren kan gjøre dette")
return await ctx.reply(text)
elif isinstance(error, commands.CommandOnCooldown): elif isinstance(error, commands.CommandOnCooldown):
text = "Kommandoen har nettopp blitt brukt\n" + f"Prøv igjen om `{error.retry_after:.1f}` sekunder." embed = embed_templates.error_warning(
return await ctx.reply(text) f"Kommandoen har nettopp blitt brukt\nPrøv igjen om `{error.retry_after:.1f}` sekunder.",
)
elif isinstance(error, commands.NoPrivateMessage): elif isinstance(error, commands.NoPrivateMessage):
try: embed = embed_templates.error_warning("Denne kommandoen kan bare utføres it servere")
text = "Denne kommandoen kan bare utføres i servere" else:
return await ctx.reply(text) embed = embed_templates.error_fatal("En ukjent feil oppstod!")
except discord.errors.Forbidden: # Thrown if bot is blocked by the user or if the user has closed their DMs f = discord.File("./src/assets/edb.png")
self.bot.logger.info("DM Blocked!") embed.set_image(url="attachment://edb.png")
elif isinstance(error, commands.DisabledCommand): # Yeah, this fucking sucks but I want my beautiful edb picture :_;
pass try:
f
except NameError:
f = None
elif isinstance(error, commands.CheckFailure): try:
return await ctx.reply(embed=embed, file=f)
except discord.errors.Forbidden:
embed = embed_templates.error_fatal(ctx, text="En ukjent feil oppstod!") self.bot.logger.warning("Could not send error message to user")
await ctx.reply(embed=embed) except app_commands.BotMissingPermissions:
await ctx.reply(
"Jeg mangler `embed_links` og/eller `attach_files` tillatelsen(e). Ingenting funker uten de"
)
# Log full exception to file # Log full exception to file
self.bot.logger.error("".join(traceback.format_exception(type(error), error, error.__traceback__))) self.bot.logger.error("".join(traceback.format_exception(type(error), error, error.__traceback__)))
@@ -134,45 +134,60 @@ class Errors(commands.Cog):
error (app_commands.AppCommandError): Eror context object error (app_commands.AppCommandError): Eror context object
""" """
# TODO: add support for defered interactions await interaction.response.defer()
# Log command usage, just in case # Log command usage, just in case
await self.on_app_command_completion(interaction, interaction.command) await self.on_app_command_completion(interaction, interaction.command)
if isinstance(error, app_commands.BotMissingPermissions): if isinstance(error, app_commands.BotMissingPermissions):
permissions = ", ".join(error.missing_permissions) embed = self.error_missing_perms(error.missing_permissions, "Jeg")
embed = embed_templates.error_warning(
interaction, text="Jeg mangler følgende tillatelser:\n\n" + f"```\n{permissions}\n```"
)
elif isinstance(error, app_commands.MissingPermissions): elif isinstance(error, app_commands.MissingPermissions):
permissions = ", ".join(error.missing_permissions) embed = self.error_missing_perms(error.missing_permissions, "Du")
embed = embed_templates.error_warning(
interaction, text="Du mangler følgende tillatelser\n\n" + f"```\n{permissions}\n```"
)
# TODO: figure this shit out. app_commands does not support this check
# elif isinstance(error, app_commands.NotOwner):
# embed = embed_templates.error_fatal(interaction, text='Bare boteieren kan gjøre dette')
# return await interaction.response.send_message(embed=embed)
elif isinstance(error, app_commands.CommandOnCooldown): elif isinstance(error, app_commands.CommandOnCooldown):
embed = embed_templates.error_warning( embed = embed_templates.error_warning(
interaction, f"Kommandoen har nettopp blitt brukt\nPrøv igjen om `{error.retry_after:.1f}` sekunder."
text="Kommandoen har nettopp blitt brukt\n" + f"Prøv igjen om `{error.retry_after:.1f}` sekunder.",
) )
else: else:
embed = embed_templates.error_fatal(interaction, text="En ukjent feil oppstod!") embed = embed_templates.error_fatal("En ukjent feil oppstod!")
f = discord.File("./src/assets/edb.png")
embed.set_image(url="attachment://edb.png")
if interaction.response.is_done(): # Yeah, this fucking sucks but I want my beautiful edb picture :_;
await interaction.followup.send(embed=embed) try:
else: f
await interaction.response.send_message(embed=embed) except NameError:
f = None
try:
await interaction.followup.send(embed=embed, file=f)
except discord.errors.Forbidden:
self.bot.logger.warning("Could not send error message to user")
except app_commands.BotMissingPermissions:
await interaction.followup.send(
"Jeg mangler `embed_links` og/eller `attach_files` tillatelsen(e). Ingenting funker uten de"
)
# Log full exception to file # Log full exception to file
self.bot.logger.error("".join(traceback.format_exception(type(error), error, error.__traceback__))) self.bot.logger.error("".join(traceback.format_exception(type(error), error, error.__traceback__)))
def error_missing_perms(self, permissions: list[str], client: str) -> discord.Embed:
"""
Missing permissions embed template.
Since it only applies to this cog it has been put here instead of the util file
Parameters
----------
permissions (list[str]): The missing permissions
client (str): Who is missing the permissions? Typically "Du" (user) or "Jeg" (bot client)
Returns
----------
(discord.Embed): The final embed
"""
permissions = ", ".join(permissions)
return embed_templates.error_warning(f"{client} mangler følgende tillatelser\n\n```{permissions}\n```")
async def setup(bot: commands.Bot): async def setup(bot: commands.Bot):
""" """
+42 -25
View File
@@ -12,11 +12,9 @@ class FunReplies(commands.Cog):
""" """
Parameters Parameters
---------- ----------
bot (commands.Bot): The bot instance bot (commands.Bot): The bot instance. In this case not used
""" """
self.bot = bot
# Cooldowns for trigger words # Cooldowns for trigger words
# #
# This is kind of a shitty way to do it but I'm too lazy to implement anything good right now # This is kind of a shitty way to do it but I'm too lazy to implement anything good right now
@@ -32,6 +30,7 @@ class FunReplies(commands.Cog):
"borgerlønn": initial_datetime, "borgerlønn": initial_datetime,
} }
@commands.Cog.listener("on_message")
async def reply_to_triggers(self, message: discord.Message): async def reply_to_triggers(self, message: discord.Message):
""" """
Replies to messages that trigger certain key words/phrases Replies to messages that trigger certain key words/phrases
@@ -44,32 +43,51 @@ class FunReplies(commands.Cog):
if message.author.bot: if message.author.bot:
return return
message_content = message.content.lower() # TODO: add ability to disable single triggers?
# Auto assign cooldown_key?
triggers = [
(r"(^|\W)borgerlønn(\W|$)", "@ sivert DE SNAKKER OM BORGERLØNN", "borgerlønn"),
(r"(^|\W)olof palme(\W|$)", "Jeg vet hvem som drepte Olof Palme 👀", "olof palme"),
(r"(^|\W)+ye+et($|\W)+", "<:Nei:826593267642662912>", "yeet"),
(r"(^|\W)skal? aldri drikke?[\w\s]*igjen($|\W)+", ":billed_cap:", "drikke"),
(r"(^|\W)(jeg?|(e|æ)(g|j)?|i) er? sivert arntzen($|\W)+", "Nei, jeg er Sivert Arntzen", "sivert"),
]
if re.search(r"(^|\W)borgerlønn(\W|$)", message_content, flags=re.IGNORECASE): for trigger in triggers:
if (datetime.now() - self.previous_invokations["borgerlønn"]).seconds > self.cooldown_seconds: regex, reply, cooldown_key = trigger
await message.reply("@ sivert DE SNAKKER OM BORGERLØNN") if await self.trigger(
self.previous_invokations["borgerlønn"] = datetime.now() message=message, regex_match=regex, reply=reply, cooldown_key=cooldown_key, regex_flags=re.IGNORECASE
):
return
elif re.search(r"(^|\W)olof palme(\W|$)", message_content, flags=re.IGNORECASE): async def trigger(
if (datetime.now() - self.previous_invokations["olof palme"]).seconds > self.cooldown_seconds: self, message: discord.Message, regex_match: str, reply: str, cooldown_key: str, regex_flags=None
await message.reply("Jeg vet hvem som drepte Olof Palme 👀") ) -> bool:
self.previous_invokations["olof palme"] = datetime.now() """
Add a trigger to the bot
elif re.search(r"(^|\W)+ye+et($|\W)+", message_content, flags=re.IGNORECASE): Parameters
if (datetime.now() - self.previous_invokations["yeet"]).seconds > self.cooldown_seconds: ----------
await message.reply("<:Nei:826593267642662912>") message (discord.Message): The message object to check for triggers
self.previous_invokations["yeet"] = datetime.now() regex_match (str): The regex pattern to match
reply (str): The reply to send
cooldown_key (str): The key to use for cooldown tracking
regex_flags (int): The regex flags to use
elif re.search(r"(^|\W)skal? aldri drikke?[\w\s]*igjen($|\W)+", message_content, flags=re.IGNORECASE): Returns
if (datetime.now() - self.previous_invokations["drikke"]).seconds > self.cooldown_seconds: ----------
await message.reply(":billed_cap:") bool: Whether or not the reply was triggered
self.previous_invokations["drikke"] = datetime.now() """
elif re.search(r"(^|\W)(jeg?|(e|æ)(g|j)?|i) er? sivert arntzen($|\W)+", message_content, flags=re.IGNORECASE): if (datetime.now() - self.previous_invokations[cooldown_key]).seconds < self.cooldown_seconds:
if (datetime.now() - self.previous_invokations["sivert"]).seconds > self.cooldown_seconds: return False
await message.reply("Nei, jeg er Sivert Arntzen!")
self.previous_invokations["sivert"] = datetime.now() if re.search(regex_match, message.content, flags=regex_flags):
await message.reply(reply)
self.previous_invokations[cooldown_key] = datetime.now()
return True
return False
async def setup(bot: commands.Bot): async def setup(bot: commands.Bot):
@@ -81,5 +99,4 @@ async def setup(bot: commands.Bot):
bot (commands.Bot): Bot instance bot (commands.Bot): Bot instance
""" """
bot.add_listener(FunReplies(bot).reply_to_triggers, "on_message")
await bot.add_cog(FunReplies(bot)) await bot.add_cog(FunReplies(bot))
+17 -16
View File
@@ -2,12 +2,11 @@ import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
from cogs.utils import discord_utils
from cogs.utils import embed_templates from cogs.utils import embed_templates
class Gullkorn(commands.Cog): class Gullkorn(commands.Cog):
"""Stand alone cog for handling whitelisting of Discord users on the Minecraft server""" """Tracks stats for gullkorn"""
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
""" """
@@ -21,7 +20,9 @@ class Gullkorn(commands.Cog):
self.init_db() self.init_db()
def init_db(self): def init_db(self):
"""Create the necessary tables for the gullkorn cog to work.""" """
Create the necessary tables for the gullkorn cog to work
"""
self.cursor.execute( self.cursor.execute(
""" """
@@ -49,7 +50,7 @@ class Gullkorn(commands.Cog):
""" """
) )
def __construct_data_string(self, data: list[tuple[int, int, int]]) -> str: def construct_data_string(self, data: list[tuple[int, int, int]]) -> str:
""" """
Constructs a formatted string displaying lists of gullkorn data Constructs a formatted string displaying lists of gullkorn data
@@ -72,6 +73,7 @@ class Gullkorn(commands.Cog):
return formatted_string return formatted_string
@commands.Cog.listener("on_message")
async def gullkorn_listener(self, message: discord.Message): async def gullkorn_listener(self, message: discord.Message):
""" """
Listens for messages in the gullkorn channel and updates the database accordingly Listens for messages in the gullkorn channel and updates the database accordingly
@@ -122,8 +124,9 @@ class Gullkorn(commands.Cog):
bruker (discord.Member, optional): Discord user to fetch stats for. Defaults to None. bruker (discord.Member, optional): Discord user to fetch stats for. Defaults to None.
""" """
# This looks kinda ugly, ngl
if bruker: if bruker:
result = self.cursor.execute( self.cursor.execute(
""" """
SELECT * SELECT *
FROM gullkorn FROM gullkorn
@@ -131,25 +134,24 @@ class Gullkorn(commands.Cog):
""", """,
(bruker.id,), (bruker.id,),
) )
result = self.cursor.fetchone() result = self.cursor.fetchone()
if not result: if not result:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal(interaction, text="Ingen data om denne brukeren funnet"), embed=embed_templates.error_warning("Ingen data om denne brukeren funnet"),
ephemeral=False, ephemeral=False,
) )
embed = discord.Embed( embed = discord.Embed(
title=f"Gullkornstatistikk for `{bruker.name}`", title=f"Gullkornstatistikk for `{bruker.name}`",
color=discord_utils.get_color(bruker), color=bruker.color,
) )
embed.set_thumbnail(url=bruker.avatar) embed.set_thumbnail(url=bruker.avatar)
embed.add_field(name="Antall gullkorn", value=result[1]) embed.add_field(name="Antall gullkorn", value=result[1])
embed.add_field(name="Antall gullkorn postet", value=result[2]) embed.add_field(name="Antall gullkorn postet", value=result[2])
return await interaction.response.send_message(embed=embed, ephemeral=False) return await interaction.response.send_message(embed=embed, ephemeral=False)
summary = self.cursor.execute( self.cursor.execute(
""" """
SELECT SUM(citations_posted) SELECT SUM(citations_posted)
FROM gullkorn; FROM gullkorn;
@@ -157,7 +159,7 @@ class Gullkorn(commands.Cog):
) )
summary = self.cursor.fetchone() summary = self.cursor.fetchone()
most_cited = self.cursor.execute( self.cursor.execute(
""" """
SELECT * SELECT *
FROM most_cited FROM most_cited
@@ -166,7 +168,7 @@ class Gullkorn(commands.Cog):
) )
most_cited = self.cursor.fetchall() most_cited = self.cursor.fetchall()
citations_posted = self.cursor.execute( self.cursor.execute(
""" """
SELECT * SELECT *
FROM most_frequent_posters FROM most_frequent_posters
@@ -175,13 +177,13 @@ class Gullkorn(commands.Cog):
) )
citations_posted = self.cursor.fetchall() citations_posted = self.cursor.fetchall()
most_cited_string = self.__construct_data_string(most_cited) most_cited_string = self.construct_data_string(most_cited)
citations_posted_string = self.__construct_data_string(citations_posted) citations_posted_string = self.construct_data_string(citations_posted)
gullkorn_first_msg = "https://canary.discord.com/channels/747542543750660178/865970753748074576/1034587913285025912" # noqa: E501 GULLKORN_FIRST_MSG = "https://canary.discord.com/channels/747542543750660178/865970753748074576/1034587913285025912" # noqa: E501
embed = discord.Embed(title="Gullkornstatistikk for serveren") embed = discord.Embed(title="Gullkornstatistikk for serveren")
embed.description = f"Antall gullkorn siden [denne meldingen]({gullkorn_first_msg}): *{summary[0]}*" embed.description = f"Antall gullkorn siden [denne meldingen]({GULLKORN_FIRST_MSG}): *{summary[0]}*"
embed.add_field(name="Mest sitert", value=most_cited_string, inline=False) embed.add_field(name="Mest sitert", value=most_cited_string, inline=False)
embed.add_field(name="Postet mest", value=citations_posted_string, inline=False) embed.add_field(name="Postet mest", value=citations_posted_string, inline=False)
await interaction.response.send_message(embed=embed, ephemeral=False) await interaction.response.send_message(embed=embed, ephemeral=False)
@@ -196,5 +198,4 @@ async def setup(bot: commands.Bot):
bot (commands.Bot): Bot instance bot (commands.Bot): Bot instance
""" """
bot.add_listener(Gullkorn(bot).gullkorn_listener, "on_message")
await bot.add_cog(Gullkorn(bot)) await bot.add_cog(Gullkorn(bot))
+53 -88
View File
@@ -1,5 +1,3 @@
from os import remove
import discord import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
@@ -24,7 +22,7 @@ class Info(commands.Cog):
guild_group = app_commands.Group(name="guild", description="Se ting om serveren") guild_group = app_commands.Group(name="guild", description="Se ting om serveren")
user_group = app_commands.Group(name="bruker", description="Se ting om brukeren") user_group = app_commands.Group(name="bruker", description="Se ting om brukeren")
def __construct_role_string(self, roles: list[discord.Role]) -> str: def construct_role_string(self, roles: list[discord.Role]) -> str:
""" """
Puts all roles except @everyone into a list and joins them to a string Puts all roles except @everyone into a list and joins them to a string
@@ -45,7 +43,7 @@ class Info(commands.Cog):
return ", ".join(roles) return ", ".join(roles)
def __construct_booster_string(self, interaction: discord.Interaction, join_method: callable = ", ".join) -> str: def construct_booster_string(self, interaction: discord.Interaction, join_method: callable = ", ".join) -> str:
""" """
Joins all boosters into a string Joins all boosters into a string
@@ -62,20 +60,17 @@ class Info(commands.Cog):
if not interaction.guild.premium_subscribers: if not interaction.guild.premium_subscribers:
return "**Ingen boostere**" return "**Ingen boostere**"
# Sort boosters by boost date and put name#discrim as well as date of boost into a list of stirngs # Sort boosters by boost date and put name as well as date of boost into a list of stirngs
# Use map instead of list comprehension because I can divide the code into 2 lines boosters = [
boosters = list( f"* {b.name} - {discord.utils.format_dt(b.premium_since)}"
map( for b in sorted(interaction.guild.premium_subscribers, key=lambda m: m.premium_since)
lambda b: f"* {b.name} - {discord.utils.format_dt(b.premium_since)}", ]
sorted(interaction.guild.premium_subscribers, key=lambda m: m.premium_since),
)
)
return join_method(boosters) return join_method(boosters)
def __construct_member_string(self, members: list[discord.Member]) -> str: def construct_member_string(self, members: list[discord.Member]) -> str:
""" """
Joins all name#discrim into a string Joins all names into a string
Parameters Parameters
---------- ----------
@@ -93,31 +88,6 @@ class Info(commands.Cog):
return ", ".join(members) return ", ".join(members)
async def __send_as_txt_file(self, interaction: discord.Interaction, content: str, file_path: str):
"""
Sends a string as a txt file and deletes the file afterwards
Parameters
----------
interaction (discord.Interaction): Slash command context object
content (str): String that's too long to send
file_path (str): Path to file
"""
# Create file
with open(file_path, "w", encoding="utf-8") as file:
file.write(content)
# Send file
txt_file = discord.File(file_path)
await interaction.response.send_message(file=txt_file)
# Delete file
try:
remove(file_path)
except (FileNotFoundError, OSError, PermissionError):
pass
@app_commands.guild_only() @app_commands.guild_only()
@app_commands.checks.bot_has_permissions(embed_links=True, external_emojis=True) @app_commands.checks.bot_has_permissions(embed_links=True, external_emojis=True)
@app_commands.checks.cooldown(1, 2) @app_commands.checks.cooldown(1, 2)
@@ -156,7 +126,7 @@ class Info(commands.Cog):
offline_members += 1 offline_members += 1
# Roles # Roles
roles = self.__construct_role_string(interaction.guild.roles) roles = self.construct_role_string(interaction.guild.roles)
roles = ( roles = (
roles roles
if len(roles) < 1024 if len(roles) < 1024
@@ -164,7 +134,7 @@ class Info(commands.Cog):
) )
# Boosts # Boosts
boosters = self.__construct_booster_string(interaction, join_method="\n".join) boosters = self.construct_booster_string(interaction, join_method="\n".join)
boosters = ( boosters = (
boosters boosters
if len(boosters) < 1024 if len(boosters) < 1024
@@ -179,31 +149,35 @@ class Info(commands.Cog):
# Features # Features
features_string = "" features_string = ""
if interaction.guild.features is not []: if interaction.guild.features != []:
features = { features = {
"ANIMATED_BANNER": "Animer serverbanner", "ANIMATED_BANNER": "Animer serverbanner",
"ANIMATED_ICON": "Animert serverikon", "ANIMATED_ICON": "Animert serverikon",
"APPLICATION_COMMAND_PERMISSIONS_V2": "Gamle slash command tillatelser",
"AUTO_MODERATION": "Automoderering",
"BANNER": "Serverbanner", "BANNER": "Serverbanner",
"COMMERCE": "E-handel",
"COMMUNITY": "Samfunnsserver", "COMMUNITY": "Samfunnsserver",
"CREATOR_MONETIZABLE_PROVISIONAL": "Betalingsmuligheter",
"CREATOR_STORE_PAGE": "Abonnomentsside",
"DEVELOPER_SUPPORT_SERVER": "Støtteserver for utviklere",
"DISCOVERABLE": "På utforsksiden", "DISCOVERABLE": "På utforsksiden",
"FEATURABLE": "", "FEATURABLE": "Fremhevbar",
"INVITES_DISABLED": "Invitasjoner deaktivert",
"INVITE_SPLASH": "Invitasjonsbilde", "INVITE_SPLASH": "Invitasjonsbilde",
"MEMBER_VERIFICATION_GATE_ENABLED": "Medlemsverifisering", "MEMBER_VERIFICATION_GATE_ENABLED": "Medlemsverifisering",
"MONETIZATION_ENABLED": "Betalingskanal",
"MORE_EMOJI": "Flere emoji",
"MORE_STICKERS": "Flere stickers", "MORE_STICKERS": "Flere stickers",
"NEWS": "Nyhetskanaler", "NEWS": "Nyhetskanaler",
"PARTNERED": "Partner", "PARTNERED": "Partner",
"PREVIEW_ENABLED": "Forhåndsvisning", "PREVIEW_ENABLED": "Forhåndsvisning",
"PRIVATE_THREADS": "Private tråder", "RAID_ALERTS_DISABLED": "Raidvarsler deaktivert",
"ROLE_ICONS": "Rolleikon", "ROLE_ICONS": "Rolleikon",
"ROLE_SUBSCRIPTIONS_AVAILABLE_FOR_PURCHASE": "Abonomentsroller tilgjengelig",
"ROLE_SUBSCRIPTIONS_ENABLED": "Abonomentsroller aktivert",
"TICKETED_EVENTS_ENABLED": "Arrangmenter med billettsalg", "TICKETED_EVENTS_ENABLED": "Arrangmenter med billettsalg",
"VANITY_URL": "Egendefinert URL", "VANITY_URL": "Egendefinert URL",
"VERIFIED": "Verifisert", "VERIFIED": "Verifisert",
"VIP_REGIONS": "Høyere bitrate i stemmekanaler", "VIP_REGIONS": "Høyere bitrate i stemmekanaler",
"WELCOME_SCREEN_ENABLED": "Velkomstskjerm", "WELCOME_SCREEN_ENABLED": "Velkomstskjerm",
"INVITES_DISABLED": "Invitasjoner deaktivert",
} }
for feature in interaction.guild.features: for feature in interaction.guild.features:
if translation := features.get(feature): if translation := features.get(feature):
@@ -232,7 +206,8 @@ class Info(commands.Cog):
description=f"* **Verifiseringskrav:** {verification}\n" description=f"* **Verifiseringskrav:** {verification}\n"
+ f"* **Innholdsfilter:** {content}\n" + f"* **Innholdsfilter:** {content}\n"
+ f"* **Boost Tier:** {interaction.guild.premium_tier}\n" + f"* **Boost Tier:** {interaction.guild.premium_tier}\n"
+ f"* **Emoji:** {len(interaction.guild.emojis)}", + f"* **Emoji:** {len(interaction.guild.emojis)}\n"
+ f"* **Stickers:** {len(interaction.guild.stickers)}\n",
) )
embed.set_author(name=interaction.guild.name, icon_url=interaction.guild.icon) embed.set_author(name=interaction.guild.name, icon_url=interaction.guild.icon)
embed.set_thumbnail(url=interaction.guild.icon) embed.set_thumbnail(url=interaction.guild.icon)
@@ -271,7 +246,7 @@ class Info(commands.Cog):
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.guild_only() @app_commands.guild_only()
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True)
@app_commands.checks.cooldown(1, 2) @app_commands.checks.cooldown(1, 2)
@guild_group.command(name="roller", description="Se rollene på serveren") @guild_group.command(name="roller", description="Se rollene på serveren")
async def guild_roles(self, interaction: discord.Interaction): async def guild_roles(self, interaction: discord.Interaction):
@@ -283,11 +258,11 @@ class Info(commands.Cog):
interaction (discord.Interaction): Slash command context object interaction (discord.Interaction): Slash command context object
""" """
roles = self.__construct_role_string(interaction.guild.roles) roles = self.construct_role_string(interaction.guild.roles)
# IF roles list is longer than 2048, create text file and send it # IF roles list is longer than 2048, create text file and send it
if len(roles) > 2048: if len(roles) > 2048:
self.__send_as_txt_file(interaction, f"./assets/temp/{interaction.guild.id}_roles.txt", roles) await discord_utils.send_as_txt_file(interaction, roles, f"./assets/temp/{interaction.guild.id}_roles.txt")
else: else:
embed = discord.Embed(color=interaction.guild.me.color, description=roles) embed = discord.Embed(color=interaction.guild.me.color, description=roles)
embed.set_author(name=f"Roller ({len(interaction.guild.roles)})", icon_url=interaction.guild.icon) embed.set_author(name=f"Roller ({len(interaction.guild.roles)})", icon_url=interaction.guild.icon)
@@ -308,10 +283,10 @@ class Info(commands.Cog):
""" """
if len(interaction.guild.premium_subscribers) == 0: if len(interaction.guild.premium_subscribers) == 0:
embed = embed_templates.error_warning(interaction, text="Serveren har ikke noen boosts :(") embed = embed_templates.error_warning("Serveren har ikke noen boosts :(")
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
boosters = self.__construct_booster_string(interaction, "\n".join) boosters = self.construct_booster_string(interaction, "\n".join)
embed = discord.Embed(color=interaction.guild.me.color, description=boosters) embed = discord.Embed(color=interaction.guild.me.color, description=boosters)
embed.set_author( embed.set_author(
@@ -352,7 +327,7 @@ class Info(commands.Cog):
""" """
if not interaction.guild.splash: if not interaction.guild.splash:
embed = embed_templates.error_warning(interaction, text="Serveren har ikke en splash") embed = embed_templates.error_warning("Serveren har ikke en splash")
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
embed = discord.Embed(color=interaction.guild.me.color, description=f"[Lenke]({interaction.guild.splash})") embed = discord.Embed(color=interaction.guild.me.color, description=f"[Lenke]({interaction.guild.splash})")
@@ -374,7 +349,7 @@ class Info(commands.Cog):
""" """
if not interaction.guild.banner: if not interaction.guild.banner:
embed = embed_templates.error_warning(interaction, text="Serveren har ikke et banner :(") embed = embed_templates.error_warning("Serveren har ikke et banner :(")
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
embed = discord.Embed(color=interaction.guild.me.color, description=f"[Lenke]({interaction.guild.banner})") embed = discord.Embed(color=interaction.guild.me.color, description=f"[Lenke]({interaction.guild.banner})")
@@ -397,7 +372,7 @@ class Info(commands.Cog):
""" """
if rolle.name == "@everyone": if rolle.name == "@everyone":
embed = embed_templates.error_fatal(interaction, text="Skriv inn en annen rolle enn @everyone") embed = embed_templates.error_warning("Skriv inn en annen rolle enn `@everyone`")
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
# Timestamp and days since creation # Timestamp and days since creation
@@ -405,15 +380,13 @@ class Info(commands.Cog):
since_created_days = (interaction.created_at - rolle.created_at).days since_created_days = (interaction.created_at - rolle.created_at).days
# List of members with the role # List of members with the role
members = self.__construct_member_string(rolle.members) members = self.construct_member_string(rolle.members)
members = members if len(members) < 1024 else "For mange medlemmer for å vise her" members = members if len(members) < 1024 else "For mange medlemmer for å vise her"
# List of permissions # List of permissions
permissions = ", ".join([permission for permission, value in iter(rolle.permissions) if value is True]) permissions = ", ".join([permission for permission, value in iter(rolle.permissions) if value is True])
embed = discord.Embed( embed = discord.Embed(title=rolle.name, description=f"{rolle.mention}\n**ID:** {rolle.id}", color=rolle.color)
title=rolle.name, description=f"{rolle.mention}\n**ID:** {rolle.id}", color=discord_utils.get_color(rolle)
)
embed.set_author(name=rolle.guild.name, icon_url=rolle.guild.icon) embed.set_author(name=rolle.guild.name, icon_url=rolle.guild.icon)
embed.add_field(name="Fargekode", value=str(rolle.color)) embed.add_field(name="Fargekode", value=str(rolle.color))
embed.add_field( embed.add_field(
@@ -443,7 +416,7 @@ class Info(commands.Cog):
kanal (discord.TextChannel): Text channel to fetch information about kanal (discord.TextChannel): Text channel to fetch information about
""" """
members = self.__construct_member_string(kanal.members) members = self.construct_member_string(kanal.members)
if len(members) > 1024: if len(members) > 1024:
members = "For mange for å vise her" members = "For mange for å vise her"
@@ -501,9 +474,10 @@ class Info(commands.Cog):
interaction (discord.Interaction): Slash command context object interaction (discord.Interaction): Slash command context object
""" """
roles = list(filter(lambda r: r.name != "@everyone", interaction.guild.roles)) roles = sorted(
roles = sorted(roles, key=lambda x: len(x.members), reverse=True) [r for r in interaction.guild.roles if r.name != "@everyone"], key=lambda x: len(x.members), reverse=True
roles_formatted = list(map(lambda r: f"**#{r[0] + 1}** {r[1].mention} - {len(r[1].members)}", enumerate(roles))) )
roles_formatted = [f"**#{i + 1}** {r.mention} - {len(r.members)}" for i, r in enumerate(roles)]
paginator = misc_utils.Paginator(roles_formatted) paginator = misc_utils.Paginator(roles_formatted)
view = discord_utils.Scroller(paginator, interaction.user) view = discord_utils.Scroller(paginator, interaction.user)
@@ -566,18 +540,14 @@ class Info(commands.Cog):
# Sort members by creation date # Sort members by creation date
members = sorted(interaction.guild.members, key=lambda m: m.created_at) members = sorted(interaction.guild.members, key=lambda m: m.created_at)
# Create list of members with index, name#discriminator and creation date # Create list of members with index, name and creation date
members_formatted = list( members_formatted = [
map( f"**#{i+1}** {m.name} - {discord.utils.format_dt(m.created_at, style='F')}" for i, m in enumerate(members)
lambda m: f'**#{(m[0] + 1)}** {m[1].name} - {discord.utils.format_dt(m[1].created_at, style="F")}', # noqa: E501 ]
enumerate(members),
)
)
paginator = misc_utils.Paginator(members_formatted) paginator = misc_utils.Paginator(members_formatted)
view = discord_utils.Scroller(paginator, interaction.user) view = discord_utils.Scroller(paginator, interaction.user)
# Send first page
embed = view.construct_embed( embed = view.construct_embed(
discord.Embed( discord.Embed(
color=interaction.guild.me.color, title="Eldste brukere på serveren basert på når de ble lagd" color=interaction.guild.me.color, title="Eldste brukere på serveren basert på når de ble lagd"
@@ -604,12 +574,9 @@ class Info(commands.Cog):
members = sorted(interaction.guild.members, key=lambda m: m.joined_at) members = sorted(interaction.guild.members, key=lambda m: m.joined_at)
# Create list of members with index, name#discriminator and creation date # Create list of members with index, name#discriminator and creation date
members_formatted = list( members_formatted = [
map( f"**#{i+1}** {m.name} - {discord.utils.format_dt(m.joined_at, style='F')}" for i, m in enumerate(members)
lambda m: f'**#{(m[0] + 1)}** {m[1].name} - {discord.utils.format_dt(m[1].joined_at, style="F")}', # noqa: E501 ]
enumerate(members),
)
)
paginator = misc_utils.Paginator(members_formatted) paginator = misc_utils.Paginator(members_formatted)
view = discord_utils.Scroller(paginator, interaction.user) view = discord_utils.Scroller(paginator, interaction.user)
@@ -666,7 +633,7 @@ class Info(commands.Cog):
premium_since_days = (interaction.created_at - bruker.premium_since).days premium_since_days = (interaction.created_at - bruker.premium_since).days
# Get user roles # Get user roles
roles = self.__construct_role_string(bruker.roles) roles = self.construct_role_string(bruker.roles)
roles = ( roles = (
roles roles
@@ -682,9 +649,7 @@ class Info(commands.Cog):
} }
status = statuses[str(bruker.status)] status = statuses[str(bruker.status)]
embed = discord.Embed( embed = discord.Embed(color=bruker.color, description=f"{bruker.mention}\nID: {bruker.id}\n{status}\n{app}")
color=discord_utils.get_color(bruker), description=f"{bruker.mention}\nID: {bruker.id}\n{status}\n{app}"
)
if bruker.display_name == bruker.name: if bruker.display_name == bruker.name:
embed.set_author(name=bruker.name, icon_url=bruker.display_avatar) embed.set_author(name=bruker.name, icon_url=bruker.display_avatar)
else: else:
@@ -736,15 +701,15 @@ class Info(commands.Cog):
if not bruker: if not bruker:
bruker = interaction.user bruker = interaction.user
roles = self.__construct_role_string(bruker.roles) roles = self.construct_role_string(bruker.roles)
# If the list of roles is too long, send it as a file # If the list of roles is too long, send it as a file
if len(roles) > 2048: if len(roles) > 2048:
self.__send_as_txt_file( await discord_utils.send_as_txt_file(
interaction, f"./assets/temp/{interaction.guild.id}_{interaction.user.id}_roles.txt", roles interaction, roles, f"./assets/temp/{interaction.guild.id}_{interaction.user.id}_roles.txt"
) )
else: else:
embed = discord.Embed(color=discord_utils.get_color(bruker), description=roles) embed = discord.Embed(color=bruker.color, description=roles)
embed.set_author(name=f"Roller ({len(bruker.roles)})", icon_url=bruker.display_avatar) embed.set_author(name=f"Roller ({len(bruker.roles)})", icon_url=bruker.display_avatar)
embed.set_footer(text=bruker.name, icon_url=bruker.display_avatar) embed.set_footer(text=bruker.name, icon_url=bruker.display_avatar)
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@@ -765,7 +730,7 @@ class Info(commands.Cog):
if not bruker: if not bruker:
bruker = interaction.user bruker = interaction.user
embed = discord.Embed(color=discord_utils.get_color(bruker), description=f"[Lenke]({bruker.display_avatar})") embed = discord.Embed(color=bruker.color, description=f"[Lenke]({bruker.display_avatar})")
embed.set_author(name=bruker.name, icon_url=bruker.display_avatar) embed.set_author(name=bruker.name, icon_url=bruker.display_avatar)
embed.set_image(url=bruker.display_avatar) embed.set_image(url=bruker.display_avatar)
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
+21 -12
View File
@@ -22,7 +22,9 @@ class MCWhitelist(commands.Cog):
self.init_db() self.init_db()
def init_db(self): def init_db(self):
"""Create the necessary tables for the mc_whitelist cog to work.""" """
Create the necessary tables for the mc_whitelist cog to work
"""
self.cursor.execute( self.cursor.execute(
""" """
@@ -50,9 +52,7 @@ class MCWhitelist(commands.Cog):
data = requests.get(f"https://api.mojang.com/users/profiles/minecraft/{minecraftbrukernavn}", timeout=10) data = requests.get(f"https://api.mojang.com/users/profiles/minecraft/{minecraftbrukernavn}", timeout=10)
if data.status_code != 200: if data.status_code != 200:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal( embed=embed_templates.error_warning(f"Brukeren `{minecraftbrukernavn}` finnes ikke på minecraft"),
interaction, text=f"Brukeren `{minecraftbrukernavn}` finnes ikke på minecraft"
),
ephemeral=True, ephemeral=True,
) )
@@ -69,16 +69,25 @@ class MCWhitelist(commands.Cog):
) )
if self.cursor.fetchone(): if self.cursor.fetchone():
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal( embed=embed_templates.error_warning(
interaction, text="Du har allerede whitelisted en bruker eller så er brukeren du oppga whitelisted" "Du har allerede whitelisted en bruker eller så er brukeren du oppga whitelisted"
) )
) )
# Whitelist user on minecraft server # Whitelist user on minecraft server
# Unfortunately, this requires an active connection to the server, with correct credentials # Unfortunately, this requires an active connection to the server, with correct credentials
with MCRcon(host="127.0.0.1", password=self.bot.mc_rcon_password, port=25575) as mcr: # Also unfortunate, we seemingly need to wrap the context manager like this to catch any exceptions
mcr.command(f'whitelist add {data["name"]}') try:
mcr.command("whitelist reload") with MCRcon(host="127.0.0.1", password=self.bot.mc_rcon_password, port=25575) as mcr:
mcr.command(f'whitelist add {data["name"]}')
mcr.command("whitelist reload")
except Exception as e:
self.bot.logger.error(f"Failed to use RCON: {e}")
return await interaction.response.send_message(
embed=embed_templates.error_fatal(
"Klarte ikke å koble til minecraftserveren. Ta kontakt med din lokale teknisk ansvarlige",
)
)
# Add user to db # Add user to db
self.cursor.execute( self.cursor.execute(
@@ -89,10 +98,10 @@ class MCWhitelist(commands.Cog):
(interaction.user.id, data["id"]), (interaction.user.id, data["id"]),
) )
self.bot.logger.info(f"Whitelisted {data['name']} for {interaction.user.name}")
await interaction.response.send_message( await interaction.response.send_message(
embed=embed_templates.success( embed=embed_templates.success(f'`{data["name"]}` er nå tilknyttet din discordbruker og whitelisted!')
interaction, text=f'`{data["name"]}` er nå tilknyttet din discordbruker og whitelisted!'
)
) )
+17 -90
View File
@@ -1,22 +1,22 @@
import functools import functools
import os import os
import textwrap
from io import BytesIO from io import BytesIO
import cv2
import discord import discord
import numpy as np
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
from moviepy.editor import CompositeVideoClip from moviepy.editor import CompositeVideoClip
from moviepy.editor import TextClip from moviepy.editor import TextClip
from moviepy.editor import VideoFileClip from moviepy.editor import VideoFileClip
from PIL import Image from PIL import Image
from PIL import ImageDraw
from PIL import ImageEnhance from PIL import ImageEnhance
from PIL import ImageFont
from PIL import UnidentifiedImageError from PIL import UnidentifiedImageError
from cogs.utils import discord_utils from cogs.utils import discord_utils
from cogs.utils import embed_templates from cogs.utils import embed_templates
from cogs.utils import misc_utils
class Meme(commands.Cog): class Meme(commands.Cog):
@@ -31,7 +31,7 @@ class Meme(commands.Cog):
self.bot = bot self.bot = bot
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(attach_files=True)
@app_commands.checks.cooldown(1, 5) @app_commands.checks.cooldown(1, 5)
@app_commands.command(name="deepfry", description="Friter et bilde til hundre og helvete") @app_commands.command(name="deepfry", description="Friter et bilde til hundre og helvete")
async def deepfry(self, interaction: discord.Interaction, bilde: discord.Attachment): async def deepfry(self, interaction: discord.Interaction, bilde: discord.Attachment):
@@ -47,14 +47,12 @@ class Meme(commands.Cog):
await interaction.response.defer() await interaction.response.defer()
# Fetch image # Fetch image
input = await discord_utils.get_file_bytesio(bilde) input_image = await discord_utils.get_file_bytesio(bilde)
# Open image
# Check if image is valid
try: try:
image = Image.open(input) image = Image.open(input_image)
except UnidentifiedImageError: except UnidentifiedImageError:
await interaction.followup.send(embed=embed_templates.error_warning(interaction, text="Bildet er ugyldig")) await interaction.followup.send(embed=embed_templates.error_warning("Bildet er ugyldig"))
return return
if image.mode != "RGB": if image.mode != "RGB":
@@ -76,79 +74,7 @@ class Meme(commands.Cog):
await interaction.followup.send(file=discord.File(output, "deepfry.jpg")) await interaction.followup.send(file=discord.File(output, "deepfry.jpg"))
@staticmethod @app_commands.checks.bot_has_permissions(attach_files=True)
def __draw_text(image: Image.Image, text: str, offset: tuple = (0, 0)):
"""
Draws meme text on preferred meme template.
Parameters
----------
image (Image.Image): The meme template.
text (str): The text to be drawn.
font (ImageFont.FreeTypeFont): The font, with its size set, to be used.
offset (tuple): The offset of the text from the top left corner of the image. Defaults to (0, 0).
"""
box_size = (540, 540)
box = Image.new("RGB", box_size, (255, 255, 255))
draw = ImageDraw.Draw(box)
font_path = "./src/assets/fonts/RobotoMono-Medium.ttf"
# Calculate initial font size
# This is done by increasing the font size until the text is too wide
max_width, max_height = box_size[0] * 0.9, box_size[1] * 0.9 # 90% of the box size
font_size = 20 # Initiale size. I consider this the smallest readable size
font_width = 0 # Initiale width. This is just to make sure the while loop runs at least once
while font_width < max_width:
font = ImageFont.truetype(font_path, font_size)
font_box = font.getbbox(text)
font_width = font_box[2] - font_box[0]
font_size += 2
# Wrap the text based on our current font size
text = textwrap.fill(text, width=font_size)
# We then have to calculate the font size again
#
# The problem is, font.getbbox() doesn't work with multiline text so it returns the size without the linebreaks
# We can get around this by calculating the size of the longest line in the text
# and then increasing the font size until that line is too wide
#
# We also have to make sure it doesn't get too tall
longest_line = max(text.split("\n"), key=len)
longest_line_font_box = font.getbbox(longest_line)
font_width = longest_line_font_box[2] - longest_line_font_box[0]
# Estimate font height
# This is done by multiplying the height of the original non-wrapped text with the number of lines
# and then adding 20 to account for the line spacing.
# This of course is dependant on font size itself, but it's good enough for now
font_height = (font_box[3] - font_box[1] + 20) * text.count("\n")
max_height = box_size[1] * 0.9
while font_width < max_width and font_height < max_height:
font = ImageFont.truetype(font_path, font_size)
longest_line_font_box = font.getbbox(longest_line)
font_width = longest_line_font_box[2] - longest_line_font_box[0]
font_box = font.getbbox(text)
font_height = (font_box[3] - font_box[1] + 20) * text.count("\n")
font_size += 2
# Draw textbox and text
text_box = draw.multiline_textbbox(box_size, text=text, font=font, align="center", anchor="mm")
text_box = box.size[0] // 2, box.size[1] // 2
draw.multiline_text(text_box, text, font=font, fill=(0, 0, 0, 255), align="center", anchor="mm")
image.paste(box, offset)
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 5) @app_commands.checks.cooldown(1, 5)
@app_commands.command(name="preferansemem", description="Generer en drake-aktig mem basert på tekst") @app_commands.command(name="preferansemem", description="Generer en drake-aktig mem basert på tekst")
async def prefer_meme(self, interaction: discord.Interaction, dårlig_tekst: str, bra_tekst: str): async def prefer_meme(self, interaction: discord.Interaction, dårlig_tekst: str, bra_tekst: str):
@@ -165,21 +91,23 @@ class Meme(commands.Cog):
await interaction.response.defer() await interaction.response.defer()
# Fetch meme template # Fetch meme template
template = Image.open("./src/assets/misc/sivert_goodbad.jpg") template = cv2.imread("./src/assets/sivert_goodbad.jpg")
# Draw text # Draw text
Meme.__draw_text(template, dårlig_tekst, offset=(540, 0)) font = "./src/assets/fonts/comic.ttf"
Meme.__draw_text(template, bra_tekst, offset=(540, 540)) await misc_utils.put_text_in_box(template, dårlig_tekst, (540, 0), (1080, 540), font)
await misc_utils.put_text_in_box(template, bra_tekst, (540, 540), (1080, 1080), font)
# Save image to buffer # Save image to buffer
output = BytesIO() _, buffer = cv2.imencode(".jpg", template)
template.save(output, format="jpeg") output = BytesIO(buffer)
cv2.imdecode(np.frombuffer(output.getbuffer(), np.uint8), -1)
output.seek(0) output.seek(0)
# Send image # Send image
await interaction.followup.send(file=discord.File(output, "sivert_goodbad.jpg")) await interaction.followup.send(file=discord.File(output, "sivert_goodbad.jpg"))
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(attach_files=True)
@app_commands.checks.cooldown(1, 60) @app_commands.checks.cooldown(1, 60)
@app_commands.command(name="crabrave", description="Generer en crab rave video basert på tekst") @app_commands.command(name="crabrave", description="Generer en crab rave video basert på tekst")
async def crab_rave(self, interaction: discord.Interaction, topptekst: str, bunntekst: str): async def crab_rave(self, interaction: discord.Interaction, topptekst: str, bunntekst: str):
@@ -204,7 +132,6 @@ class Meme(commands.Cog):
os.remove(temp_file) os.remove(temp_file)
except (FileNotFoundError, PermissionError): except (FileNotFoundError, PermissionError):
self.bot.logger.warn(f"Failed to remove temporary file {temp_file}") self.bot.logger.warn(f"Failed to remove temporary file {temp_file}")
pass
@staticmethod @staticmethod
def make_crab(top_text: str, bottom_text: str, user_id: int): def make_crab(top_text: str, bottom_text: str, user_id: int):
@@ -220,7 +147,7 @@ class Meme(commands.Cog):
""" """
font_path = "DejaVu-Sans-Bold" font_path = "DejaVu-Sans-Bold"
clip = VideoFileClip("./src/assets/misc/crab.mp4") clip = VideoFileClip("./src/assets/crab.mp4")
top_part = ( top_part = (
TextClip(top_text, fontsize=60, color="white", stroke_width=2, stroke_color="black", font=font_path) TextClip(top_text, fontsize=60, color="white", stroke_width=2, stroke_color="black", font=font_path)
+28 -84
View File
@@ -1,6 +1,5 @@
import random import random
import re import re
import urllib
from datetime import datetime from datetime import datetime
from hashlib import md5 from hashlib import md5
from io import BytesIO from io import BytesIO
@@ -29,7 +28,6 @@ class Misc(commands.Cog):
self.bot = bot self.bot = bot
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2) @app_commands.checks.cooldown(1, 2)
@app_commands.command(name="weeb", description="Kjeft på weebs") @app_commands.command(name="weeb", description="Kjeft på weebs")
async def weeb(self, interaction: discord.Interaction): async def weeb(self, interaction: discord.Interaction):
@@ -56,16 +54,24 @@ class Misc(commands.Cog):
tekst (str): Text to translate tekst (str): Text to translate
""" """
owo_rules = {"r": "w", "l": "w", "R": "W", "L": "W", "n": "ny", "N": "Ny", "ove": "uv"} # Decades of computer sciencetist optimizng every little detail to make
for key, value in owo_rules.items(): # our computers as fast as possible. Only for me to ruin it by doing this shit.
tekst = re.sub(key, value, tekst) # I am sorry to our forefathers whose shoulders we stand upon.
# https://kaomoji.moe/ # I have sinned
tekst = re.sub(r"[rl]", "w", tekst)
tekst = re.sub(r"[RL]", "W", tekst)
tekst = re.sub(r"n([aeiou])", r"ny\1", tekst, flags=re.IGNORECASE)
tekst = re.sub(r"N([aeiou])", r"Ny\1", tekst)
tekst = re.sub(r"th", "d", tekst, flags=re.IGNORECASE)
tekst = tekst.replace("ove", "uv")
# TODO: add kaomoji? https://kaomoji.moe/
if len(tekst) >= 1000: if len(tekst) >= 1000:
embed = embed_templates.error_warning(interaction, text="Teksten er for lang") embed = embed_templates.error_warning("Teksten er for lang")
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
embed = discord.Embed(color=interaction.client.user.color, description=tekst) embed = discord.Embed(description=tekst)
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True)
@@ -117,15 +123,11 @@ class Misc(commands.Cog):
params = {"action": "autocomplete", "service": "emner", "scope": "/studier/emner", "q": emnekode, "limit": 1} params = {"action": "autocomplete", "service": "emner", "scope": "/studier/emner", "q": emnekode, "limit": 1}
data = requests.get("https://www.uio.no/studier/", params=params, timeout=10) data = requests.get("https://www.uio.no/studier/", params=params, timeout=10)
if data.status_code != 200: if data.status_code != 200:
return await interaction.response.send_message( return await interaction.response.send_message(embed=embed_templates.error_fatal("Kunne ikke nå API"))
embed=embed_templates.error_fatal(interaction, text="Kunne ikke nå API")
)
results = data.text.strip("\n").split("\n")[:-1] results = data.text.strip("\n").split("\n")[:-1]
if not results: if not results:
return await interaction.response.send_message( return await interaction.response.send_message(embed=embed_templates.error_warning("Fant ikke emnekode"))
embed=embed_templates.error_fatal(interaction, text="Fant ikke emnekode")
)
for result in results: for result in results:
code, name, _ = result.split(";") code, name, _ = result.split(";")
@@ -148,10 +150,10 @@ class Misc(commands.Cog):
""" """
if len(tekst) >= 1000: if len(tekst) >= 1000:
embed = embed_templates.error_warning(interation, text="Teksten er for lang") embed = embed_templates.error_warning("Teksten er for lang")
return await interation.response.send_message(embed=embed) return await interation.response.send_message(embed=embed)
tekst = re.sub(" ", "👏", tekst) tekst = tekst.replace(" ", "👏")
embed = discord.Embed(color=interation.client.user.color, description=f"**{tekst.upper()}**") embed = discord.Embed(color=interation.client.user.color, description=f"**{tekst.upper()}**")
await interation.response.send_message(embed=embed) await interation.response.send_message(embed=embed)
@@ -169,6 +171,7 @@ class Misc(commands.Cog):
tekst (str): Text to clapify tekst (str): Text to clapify
""" """
# TODO: make this not horrible. I can't be botherd to right now
words = tekst.split(" ") words = tekst.split(" ")
for i, word in enumerate(words): for i, word in enumerate(words):
karakter = 0 karakter = 0
@@ -188,60 +191,6 @@ class Misc(commands.Cog):
embed = discord.Embed(color=interation.client.user.color, description=text) embed = discord.Embed(color=interation.client.user.color, description=text)
await interation.response.send_message(content=mention, embed=embed) await interation.response.send_message(content=mention, embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 5)
@app_commands.command(name="imdb", description="Søk etter filmer på IMDB")
async def imdb(self, interaction: discord.Interaction, tittel: str):
"""
Search for movies on IMDB and displays information about the first result
Parameters
----------
interaction (discord.Interaction): Slash command context object
tittel (str): Movie title
"""
# Search for movie/show
url = "http://www.omdbapi.com/?" + urllib.parse.urlencode({"s": tittel, "apikey": self.bot.api_keys["omdb"]})
search = requests.get(url, timeout=10).json()
try:
best_result_id = search["Search"][0]["imdbID"]
except KeyError:
embed = embed_templates.error_fatal(interaction, text="Fant ikke filmen!")
return await interaction.response.send_message(embed=embed)
# Get movie/show info
data = requests.get(
f'http://www.omdbapi.com/?i={best_result_id}&apikey={self.bot.api_keys["omdb"]}', timeout=10
).json()
acceptable_media_types = ["movie", "series", "episode"]
if data["Type"] not in acceptable_media_types:
embed = embed_templates.error_fatal(interaction, text="Fant ikke filmen!")
return await interaction.response.send_message(embed=embed)
embed = discord.Embed(
title=f'{data["Title"]} ({data["Year"]})',
color=0xF5C518,
url=f'https://www.imdb.com/title/{data["imdbID"]}/',
)
embed.set_author(name=interaction.user.name, icon_url=interaction.user.display_avatar)
embed.add_field(name="Type", value=data["Type"].title())
embed.add_field(name="Sjanger", value=data["Genre"])
embed.add_field(name="Spilletid", value=data["Runtime"])
embed.add_field(name="Vurdering på IMDb", value=f'{data["imdbRating"]}/10')
release_date = datetime.strptime(data["Released"], "%d %b %Y")
embed.add_field(name="Utgitt", value=discord.utils.format_dt(release_date, style="D"))
if data["Poster"] != "N/A":
embed.set_thumbnail(url=data["Poster"])
if data["Director"] != "N/A":
embed.description = data["Director"]
if data["Plot"] != "N/A" and len(data["Plot"]) < 1024:
embed.add_field(name="Sammendrag", value=data["Plot"], inline=False)
await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 5) @app_commands.checks.cooldown(1, 5)
@app_commands.command(name="helligdager", description="Se helligdager i et land for et år") @app_commands.command(name="helligdager", description="Se helligdager i et land for et år")
@@ -257,13 +206,11 @@ class Misc(commands.Cog):
""" """
land = land.upper() land = land.upper()
aar = datetime.now().year if not år else int(år) år = datetime.now().year if not år else int(år)
data = requests.get(f"https://date.nager.at/api/v2/publicholidays/{aar}/{land}", timeout=10) data = requests.get(f"https://date.nager.at/api/v2/publicholidays/{år}/{land}", timeout=10)
if data.status_code != 200: if data.status_code != 200:
embed = embed_templates.error_fatal( embed = embed_templates.error_warning("Ugyldig land\nHusk å bruke landskoder\n" + "For eksempel: `NO`")
interaction, text="Ugyldig land\nHusk å bruke landskoder\n" + "For eksempel: `NO`"
)
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
data = data.json() data = data.json()
@@ -277,12 +224,12 @@ class Misc(commands.Cog):
holiday_str += f'* **{date}**: {day["localName"]}\n' holiday_str += f'* **{date}**: {day["localName"]}\n'
embed = discord.Embed( embed = discord.Embed(
color=interaction.client.user.color, title=f":flag_{country}: Helligdager {aar} :flag_{country}:" color=interaction.client.user.color, title=f":flag_{country}: Helligdager {år} :flag_{country}:"
) )
embed.description = holiday_str embed.description = holiday_str
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True)
@app_commands.checks.cooldown(1, 5) @app_commands.checks.cooldown(1, 5)
@app_commands.command(name="match", description="Se hvor mye du matcher med en annen") @app_commands.command(name="match", description="Se hvor mye du matcher med en annen")
async def match(self, interaction: discord.Interaction, bruker: discord.Member): async def match(self, interaction: discord.Interaction, bruker: discord.Member):
@@ -296,9 +243,7 @@ class Misc(commands.Cog):
""" """
if bruker == interaction.user: if bruker == interaction.user:
embed = embed_templates.error_warning( embed = embed_templates.error_warning("Jeg vet du er ensom, men du kan ikke matche med deg selv")
interaction, text="Jeg vet du er ensom, men du kan ikke matche med deg selv"
)
return await interaction.response.send_message(embed=embed) return await interaction.response.send_message(embed=embed)
invoker_id = int(str(interaction.user.id)[11:14]) invoker_id = int(str(interaction.user.id)[11:14])
@@ -320,8 +265,8 @@ class Misc(commands.Cog):
user = user.resize((389, 389)) user = user.resize((389, 389))
# Prepare heart image # Prepare heart image
heart = Image.open("./src/assets/misc/heart.png") heart = Image.open("./src/assets/heart.png")
mask = Image.open("./src/assets/misc/heart.png", "r") mask = Image.open("./src/assets/heart.png", "r")
# Putting it all together # Putting it all together
image = Image.new("RGBA", (1024, 576)) image = Image.new("RGBA", (1024, 576))
@@ -329,7 +274,7 @@ class Misc(commands.Cog):
image.paste(user, (635, 94)) image.paste(user, (635, 94))
image.paste(heart, (311, 94), mask=mask) image.paste(heart, (311, 94), mask=mask)
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
font = ImageFont.truetype("./src/assets/fonts/RobotoMono-Medium.ttf", 86) font = ImageFont.truetype("./src/assets/fonts/comic.ttf", 86)
font_size = font.getbbox(f"{match_percent}%") font_size = font.getbbox(f"{match_percent}%")
font_size = ((image.size[0] - font_size[2]) / 2, (image.size[1] - font_size[3]) / 2) font_size = ((image.size[0] - font_size[2]) / 2, (image.size[1] - font_size[3]) / 2)
draw.text(font_size, f"{match_percent}%", font=font, fill=(255, 255, 255, 255)) draw.text(font_size, f"{match_percent}%", font=font, fill=(255, 255, 255, 255))
@@ -345,7 +290,6 @@ class Misc(commands.Cog):
embed.set_image(url=f"attachment://{interaction.user.id}_{bruker.id}_match.png") embed.set_image(url=f"attachment://{interaction.user.id}_{bruker.id}_match.png")
await interaction.response.send_message(embed=embed, file=f) await interaction.response.send_message(embed=embed, file=f)
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2) @app_commands.checks.cooldown(1, 2)
@app_commands.command(name="ifonlyyouknew", description="How bad things really are") @app_commands.command(name="ifonlyyouknew", description="How bad things really are")
async def howbadthingsreallyare(self, interaction: discord.Interaction): async def howbadthingsreallyare(self, interaction: discord.Interaction):
+19 -18
View File
@@ -10,23 +10,20 @@ class RepeatedMessages(commands.Cog):
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
self.bot = bot self.bot = bot
self.general_channel = 747542544291987597
# @aiocron.crontab('37 13 * * *') # Yes we have to have these inside the constructor
# async def leet(): # Probably could've implemented the waiting functions myself
# """Sends a message at leet gamer hours""" # but that sounds like work and I don't like to work
# guild = self.bot.get_guild(747542543750660178)
# channel = guild.get_channel(747542544291987597)
# sticker = await guild.fetch_sticker(1046948904471633951)
# await asyncio.sleep(1)
# await channel.send(stickers=[sticker])
@aiocron.crontab("0 0 * * 5") @aiocron.crontab("0 0 * * 5")
async def fredag(): async def fredag():
"""Sends a message on Friday at 00:00""" """
Sends a message on Friday at 00:00
"""
guild = self.bot.get_guild(747542543750660178) guild = self.bot.get_guild(self.bot.UIO_GAMING_GUILD_ID)
channel = guild.get_channel(747542544291987597) channel = guild.get_channel(self.general_channel)
videos = [ videos = [
"https://cdn.discordapp.com/attachments/750052141346979850/851216786259181578/video0.mp4", "https://cdn.discordapp.com/attachments/750052141346979850/851216786259181578/video0.mp4",
@@ -48,10 +45,12 @@ class RepeatedMessages(commands.Cog):
@aiocron.crontab("0 0 * * 1") @aiocron.crontab("0 0 * * 1")
async def mandag(): async def mandag():
"""Sends a message on Monday at 00:00""" """
Sends a message on Monday at 00:00
"""
guild = self.bot.get_guild(747542543750660178) guild = self.bot.get_guild(self.bot.UIO_GAMING_GUILD_ID)
channel = guild.get_channel(747542544291987597) channel = guild.get_channel(self.general_channel)
await channel.send( await channel.send(
"ENDELIG MANDAG!\n\n" "ENDELIG MANDAG!\n\n"
# + "https://cdn.discordapp.com/attachments/678396498089738250/862853827278929940/hvorfor_de_rike_br_spises.mp4" # noqa: E501 # + "https://cdn.discordapp.com/attachments/678396498089738250/862853827278929940/hvorfor_de_rike_br_spises.mp4" # noqa: E501
@@ -60,10 +59,12 @@ class RepeatedMessages(commands.Cog):
@aiocron.crontab("0 0 1 1 *") @aiocron.crontab("0 0 1 1 *")
async def new_year(): async def new_year():
"""Sends a message on New Year's Day at 00:00""" """
Sends a message on New Year's Day at 00:00
"""
guild = self.bot.get_guild(747542543750660178) guild = self.bot.get_guild(self.bot.UIO_GAMING_GUILD_ID)
channel = guild.get_channel(747542544291987597) channel = guild.get_channel(self.general_channel)
await channel.send( await channel.send(
"GODT NYTTÅR!\n\nNå, meld dere inn i UiO Gaming igjen <:ThisIsAThreat:874998234072903710>" "GODT NYTTÅR!\n\nNå, meld dere inn i UiO Gaming igjen <:ThisIsAThreat:874998234072903710>"
) )
+64 -45
View File
@@ -19,23 +19,10 @@ from cogs.utils import misc_utils
- score for å bli pinged i uio gullkorn - score for å bli pinged i uio gullkorn
+ score for å få melding på stjernetavla/bli stjerna. multiplier per stjerne + score for å få melding på stjernetavla/bli stjerna. multiplier per stjerne
- score hver dag med weebrolle (lite) - score hver dag med weebrolle (lite)
"""
"""
------ TO-DO ------ ------ TO-DO ------
+ score for guten tag
- score sitte i afk - score sitte i afk
+ score for å sitte i voice per time + score for å sitte i voice per time
---------------
- score for winiie the pooh
- score for memes i general
- score for å snakke negativt om borgerlønn
- score for å snakke negativt om styret
* hvis lav score, spiller av kinesisk nasjonalsang på voicechannel når joiner
daglig uthengig av laveste score
""" """
@@ -55,7 +42,9 @@ class SocialCredit(commands.Cog):
self.init_db() self.init_db()
def init_db(self): def init_db(self):
"""Create the necessary tables for the social credit cog to work""" """
Create the necessary tables for the social credit cog to work
"""
self.cursor.execute( self.cursor.execute(
""" """
@@ -76,13 +65,15 @@ class SocialCredit(commands.Cog):
self = args[0] self = args[0]
self.cursor.execute("SELECT user_id FROM social_credit WHERE user_id = %s", (args[1],)) self.cursor.execute("SELECT user_id FROM social_credit WHERE user_id = %s", (args[1],))
if not self.cursor.fetchone(): if not self.cursor.fetchone():
self._add_citizen(args[1]) self.add_citizen(args[1])
await func(*args, **kwargs) await func(*args, **kwargs)
return wrapper return wrapper
def roll(percent: int = 50): def roll(percent: int = 50):
"""Decorator that executes the function with a given percent chance""" """
Decorator that executes the function with a given percent chance
"""
def decorator(func): def decorator(func):
async def wrapper(*args, **kwargs): async def wrapper(*args, **kwargs):
@@ -93,7 +84,7 @@ class SocialCredit(commands.Cog):
return decorator return decorator
def _add_citizen(self, user_id: int): def add_citizen(self, user_id: int):
""" """
Adds a user to the database Adds a user to the database
@@ -111,21 +102,45 @@ class SocialCredit(commands.Cog):
(user_id, self.START_POINTS), (user_id, self.START_POINTS),
) )
@tasks.loop(hours=24.0, reconnect=True) @tasks.loop(time=misc_utils.MIDNIGHT, reconnect=True)
async def fuck_uwu(self): async def fuck_uwu(self):
"""Deducts points from people with the weeb role every 24 hours""" """
Deducts points from people with the weeb role every 24 hours
"""
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
weeb_role = self.bot.get_guild(747542543750660178).get_role(803629993539403826) # We need to do these checks in case the bot user does not have access to the UiO Gaming Discord server
if not (guild := self.bot.get_guild(self.bot.UIO_GAMING_GUILD_ID)):
self.bot.logger.warning(
"Could not fetch UiO Gaming guild."
+ "If the bot does not have access to the UiO Gaming server, this function won't work. "
+ "If it is, ignore this."
)
return
if not (weeb_role := guild.get_role(803629993539403826)):
self.bot.logger.warning(
"Could not fetch UiO Gaming's weeb role."
+ "If the bot does not have access to the UiO Gaming server, this function won't work. "
+ "If it is, ignore this."
)
return
for weeb in weeb_role.members: for weeb in weeb_role.members:
await self.social_punishment(weeb.id, 1, "weeb") await self.social_punishment(weeb.id, 1, "weeb")
@fuck_uwu.before_loop @fuck_uwu.before_loop
async def before_fuck_uwu(self): async def before_fuck_uwu(self):
"""Syncs loop with the time of day""" """
Wait until bot cache is ready
"""
await discord_utils.sleep_until_midnight(self.bot) await self.bot.wait_until_ready()
async def cog_unload(self):
self.bot.logger.info("Unloading cog")
self.fuck_uwu.cancel()
self.cursor.close()
@add_new_citizen @add_new_citizen
async def social_punishment(self, user_id: int, points: int, reason: str): async def social_punishment(self, user_id: int, points: int, reason: str):
@@ -173,6 +188,8 @@ class SocialCredit(commands.Cog):
social_credit_group = app_commands.Group(name="socialcredit", description="Trenger dette å forklares?") social_credit_group = app_commands.Group(name="socialcredit", description="Trenger dette å forklares?")
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@social_credit_group.command(name="credits", description="Sjekk hvor dårlig menneske du er") @social_credit_group.command(name="credits", description="Sjekk hvor dårlig menneske du er")
async def credits(self, interaction: discord.Interaction, *, bruker: discord.Member | None = None): async def credits(self, interaction: discord.Interaction, *, bruker: discord.Member | None = None):
""" """
@@ -187,12 +204,18 @@ class SocialCredit(commands.Cog):
if not bruker: if not bruker:
bruker = interaction.user bruker = interaction.user
self.cursor.execute("SELECT * FROM social_credit WHERE user_id = %s", (bruker.id,)) self.cursor.execute(
"""
SELECT * FROM social_credit
WHERE user_id = %s"
""",
(bruker.id,),
)
result = self.cursor.fetchone() result = self.cursor.fetchone()
if not result: if not result:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_fatal(interaction, f"{bruker.mention} er ikke registrert i databasen") embed=embed_templates.error_warning(f"{bruker.mention} er ikke registrert i databasen")
) )
db_user = CreditUser(*result) db_user = CreditUser(*result)
@@ -200,6 +223,8 @@ class SocialCredit(commands.Cog):
embed = discord.Embed(description=(f"{bruker.mention} har `{db_user.credit_score}` social credits")) embed = discord.Embed(description=(f"{bruker.mention} har `{db_user.credit_score}` social credits"))
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@social_credit_group.command(name="leaderboard", description="Sjekk hvem som er de beste og verste borgerne") @social_credit_group.command(name="leaderboard", description="Sjekk hvem som er de beste og verste borgerne")
async def leaderboard(self, interaction: discord.Interaction): async def leaderboard(self, interaction: discord.Interaction):
""" """
@@ -218,18 +243,14 @@ class SocialCredit(commands.Cog):
ORDER BY credit_score DESC ORDER BY credit_score DESC
""" """
) )
result = self.cursor.fetchall()
if not (result := self.cursor.fetchall()): if not result:
return await interaction.send( return await interaction.send(
embed=embed_templates.error_fatal(interaction, "Ingen brukere er registrert i databasen") embed=embed_templates.error_warning("Ingen brukere er registrert i databasen")
) )
leaderboard_formatted = list( leaderboard_formatted = [f"**#{s[0]+1}** <@{s[1][0]}> - `{s[1][1]}` poeng" for s in enumerate(result)]
map(
lambda s: f"**#{s[0]+1}** <@{s[1][0]}> - `{s[1][1]}` poeng",
enumerate(result),
)
)
paginator = misc_utils.Paginator(leaderboard_formatted) paginator = misc_utils.Paginator(leaderboard_formatted)
view = discord_utils.Scroller(paginator, interaction.user) view = discord_utils.Scroller(paginator, interaction.user)
@@ -321,10 +342,10 @@ class SocialCredit(commands.Cog):
message (discord.Message): The message object message (discord.Message): The message object
""" """
if message.channel.id == 865970753748074576: # This only works in the UiO Gaming server as well
if message.mentions: if message.channel.id == 865970753748074576 and message.mentions:
for mention in message.mentions: for mention in message.mentions:
await self.social_punishment(mention.id, 10, "gullkorn") await self.social_punishment(mention.id, 10, "gullkorn")
@commands.Cog.listener("on_reaction_add") @commands.Cog.listener("on_reaction_add")
async def on_star_add(self, reaction: discord.Reaction, user: discord.User | discord.Member): async def on_star_add(self, reaction: discord.Reaction, user: discord.User | discord.Member):
@@ -343,12 +364,11 @@ class SocialCredit(commands.Cog):
if reaction.emoji == "⭐": if reaction.emoji == "⭐":
if reaction.message.author == user: if reaction.message.author == user:
await self.social_punishment(user.id, 100, "self-star") await self.social_punishment(user.id, 100, "self-star")
else: elif reaction.count == 3:
if len(reaction.message.reactions) >= 3: await self.social_punishment(
await self.social_punishment( user.id, (len(reaction.message.reactions) - 1) * 25, "remove already accumulated stars"
user.id, (len(reaction.message.reactions) - 1) * 25, "remove already accumulated stars" )
) await self.social_reward(user.id, 25 * len(reaction.message.reactions), "add new stars")
await self.social_reward(user.id, 25 * len(reaction.message.reactions), "add new stars")
@commands.Cog.listener("on_reaction_remove") @commands.Cog.listener("on_reaction_remove")
async def on_star_remove(self, reaction: discord.Reaction, user: discord.User | discord.Member): async def on_star_remove(self, reaction: discord.Reaction, user: discord.User | discord.Member):
@@ -359,9 +379,8 @@ class SocialCredit(commands.Cog):
if user.bot: if user.bot:
return return
if reaction.emoji == "⭐": if reaction.emoji == "⭐" and reaction.count >= 3:
if len(reaction.message.reactions) >= 3: await self.social_punishment(user.id, 25, "remove star")
await self.social_punishment(user.id, 25, "remove star")
async def setup(bot: commands.Bot): async def setup(bot: commands.Bot):
+39 -35
View File
@@ -26,14 +26,17 @@ class Streak(commands.Cog):
self.cursor = self.bot.db_connection.cursor() self.cursor = self.bot.db_connection.cursor()
self.init_db() self.init_db()
# Cache to avoid having to insert to the db for every message
self.streak_cache = {} self.streak_cache = {}
self.populate_cache() self.populate_cache()
self.streak_check.start() self.streak_check.start()
self.streak_update_loop.start() self.insert_cache_loop.start()
def init_db(self): def init_db(self):
"""Create the necessary tables for the streak cog to work.""" """
Create the necessary tables for the streak cog to work
"""
self.cursor.execute( self.cursor.execute(
""" """
@@ -47,11 +50,9 @@ class Streak(commands.Cog):
) )
async def cog_unload(self): async def cog_unload(self):
"""Stop the loops and close the database connection""" self.bot.logger.info("Unloading cog")
self.insert_cache_loop.cancel()
await self.streak_update()
self.streak_check.cancel() self.streak_check.cancel()
self.streak_update_loop.cancel()
self.cursor.close() self.cursor.close()
def populate_cache(self): def populate_cache(self):
@@ -98,17 +99,25 @@ class Streak(commands.Cog):
} }
@tasks.loop(minutes=10) @tasks.loop(minutes=10)
async def streak_update_loop(self): async def insert_cache_loop(self):
"""
Inserts cache into the database every 10 minutes
"""
await self.insert_cache()
@insert_cache_loop.after_loop
async def on_insert_cache_loop_cancel(self):
self.bot.logger.info("Insert cache loop cancelled")
if self.insert_cache_loop.is_being_cancelled() and self.streak_cache:
await self.insert_cache()
async def insert_cache(self):
""" """
Inserts cache into the database Inserts cache into the database
""" """
await self.streak_update() self.bot.logger.info("Inserting cache into database")
async def streak_update(self):
"""
Inserts cache into the database
"""
# Insert into database # Insert into database
for user_id, user_data in self.streak_cache.items(): for user_id, user_data in self.streak_cache.items():
@@ -128,14 +137,15 @@ class Streak(commands.Cog):
), ),
) )
@tasks.loop(hours=24) @tasks.loop(time=misc_utils.MIDNIGHT)
async def streak_check(self): async def streak_check(self):
""" """
Check if anyone has lost their streak Check if anyone has lost their streak
""" """
# Clear cache to make sure all streaks are up to date # Clear cache to make sure all streaks are up to date
await self.streak_update() if self.streak_cache:
await self.insert_cache()
self.cursor.execute( self.cursor.execute(
""" """
@@ -156,15 +166,16 @@ class Streak(commands.Cog):
(streak[1],), (streak[1],),
) )
@streak_check.before_loop @streak_check.after_loop
async def before_streak_check(self): async def on_streak_check_cancel(self):
"""Syncs loop to the time of day""" self.bot.logger.info("Streak check loop cancelled")
if self.streak_check.is_being_cancelled():
self.bot.logger.info("Postponing streak check until midnight") await self.streak_check()
await discord_utils.sleep_until_midnight(self.bot)
streak_group = app_commands.Group(name="streak", description="Snapchat streaks, men for Discord") streak_group = app_commands.Group(name="streak", description="Snapchat streaks, men for Discord")
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@streak_group.command(name="se", description="Se din eller en annen brukers streak") @streak_group.command(name="se", description="Se din eller en annen brukers streak")
async def streak_user(self, interaction: discord.Interaction, bruker: discord.Member | discord.User | None = None): async def streak_user(self, interaction: discord.Interaction, bruker: discord.Member | discord.User | None = None):
""" """
@@ -189,13 +200,10 @@ class Streak(commands.Cog):
""", """,
(bruker.id,), (bruker.id,),
) )
streak = self.cursor.fetchone() streak = self.cursor.fetchone()
if not streak: if not streak:
return await interaction.followup.send( return await interaction.followup.send(embed=embed_templates.error_warning("Brukeren har ikke noen streak"))
embed=embed_templates.error_warning(interaction, "Brukeren har ikke noen streak")
)
streak_msg_channel, streak_msg_id = streak[0].split("-") streak_msg_channel, streak_msg_id = streak[0].split("-")
streak_msg_channel, streak_msg_id = int(streak_msg_channel), int(streak_msg_id) streak_msg_channel, streak_msg_id = int(streak_msg_channel), int(streak_msg_id)
@@ -225,6 +233,8 @@ class Streak(commands.Cog):
) )
await interaction.followup.send(embed=embed) await interaction.followup.send(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@streak_group.command(name="topp", description="Se hvem som har høyest streak på serveren") @streak_group.command(name="topp", description="Se hvem som har høyest streak på serveren")
async def streak_top(self, interaction: discord.Interaction): async def streak_top(self, interaction: discord.Interaction):
""" """
@@ -244,20 +254,14 @@ class Streak(commands.Cog):
ORDER BY streak_start_time; ORDER BY streak_start_time;
""" """
) )
streaks = self.cursor.fetchall() streaks = self.cursor.fetchall()
if not streaks: if not streaks:
return await interaction.followup.send( return await interaction.followup.send(embed=embed_templates.error_warning("Ingen har noen streak enda"))
embed=embed_templates.error_warning(interaction, "Ingen har noen streak enda")
)
streaks_formatted = list( streaks_formatted = [
map( f"**#{s[0]+1}** <@{s[1][0]}> - {(datetime.now() - s[1][1]).days} dager" for s in enumerate(streaks)
lambda s: f"**#{s[0]+1}** <@{s[1][0]}> - {(datetime.now() - s[1][1]).days} dager", ]
enumerate(streaks),
)
)
paginator = misc_utils.Paginator(streaks_formatted) paginator = misc_utils.Paginator(streaks_formatted)
view = discord_utils.Scroller(paginator, interaction.user) view = discord_utils.Scroller(paginator, interaction.user)
+2 -3
View File
@@ -16,7 +16,6 @@ class TempVoice(commands.Cog):
bot (commands.Bot): The bot instance bot (commands.Bot): The bot instance
""" """
self.bot = bot
self.temp_voice_helper = TempVoiceHelper(bot) self.temp_voice_helper = TempVoiceHelper(bot)
@commands.Cog.listener() @commands.Cog.listener()
@@ -29,6 +28,7 @@ class TempVoice(commands.Cog):
await self.temp_voice_helper.on_voice_state_update(member, before, after) await self.temp_voice_helper.on_voice_state_update(member, before, after)
@app_commands.checks.bot_has_permissions(manage_channels=True, embed_links=True)
@app_commands.checks.cooldown(1, 300) @app_commands.checks.cooldown(1, 300)
@app_commands.command(name="tempvoice") @app_commands.command(name="tempvoice")
async def tempvoice(self, interaction: discord.Interaction, name: str, limit: int = 0): async def tempvoice(self, interaction: discord.Interaction, name: str, limit: int = 0):
@@ -46,8 +46,7 @@ class TempVoice(commands.Cog):
await interaction.response.send_message( await interaction.response.send_message(
embed=embed_templates.success( embed=embed_templates.success(
interaction, f"{channel.mention} ble opprettet. Den vil bli slettet etter cirka 1 minutt uten aktivitet.",
text=f"{channel.mention} ble opprettet. Den vil bli slettet etter cirka 1 minutt uten aktivitet.",
) )
) )
+15 -19
View File
@@ -14,7 +14,6 @@ import discord
from discord import app_commands from discord import app_commands
from discord.ext import commands from discord.ext import commands
from cogs.utils import discord_utils
from cogs.utils import embed_templates from cogs.utils import embed_templates
from cogs.utils.discord_utils import Lobby from cogs.utils.discord_utils import Lobby
from cogs.utils.discord_utils import LobbyView from cogs.utils.discord_utils import LobbyView
@@ -54,8 +53,7 @@ class TenMan(commands.Cog):
else: else:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning( embed=embed_templates.error_warning(
interaction, "Du hoster allerede en lobby",
text="Du hoster allerede en lobby",
) )
) )
@@ -63,8 +61,7 @@ class TenMan(commands.Cog):
if interaction.user.id in lobby.players: if interaction.user.id in lobby.players:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning( embed=embed_templates.error_warning(
interaction, "Du er allerede i en lobby",
text="Du er allerede i en lobby",
) )
) )
@@ -80,7 +77,7 @@ class TenMan(commands.Cog):
embed = discord.Embed( embed = discord.Embed(
title="10 Man Lobby", title="10 Man Lobby",
description=f"Lobbyen stenger {time_left}. Bli med innen da!", description=f"Lobbyen stenger {time_left}. Bli med innen da!",
color=discord_utils.get_color(interaction.user), color=interaction.user.color,
) )
embed.set_author(name=interaction.user.global_name, icon_url=interaction.user.avatar) embed.set_author(name=interaction.user.global_name, icon_url=interaction.user.avatar)
embed.add_field(name="Spillere", value=f"* {interaction.user.mention}", inline=False) embed.add_field(name="Spillere", value=f"* {interaction.user.mention}", inline=False)
@@ -97,11 +94,11 @@ class TenManView(LobbyView):
@override @override
async def start_lobby(self, interaction: discord.Interaction, button: discord.ui.Button): async def start_lobby(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user != self.lobby.host: if interaction.user != self.lobby.host:
embed = embed_templates.error_warning(interaction, text="Bare hosten kan starte") embed = embed_templates.error_warning("Bare hosten kan starte")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if len(self.lobby.players) < 3: if len(self.lobby.players) < 3:
embed = embed_templates.error_warning(interaction, text="Det må være mer enn 2 i lobbyen") embed = embed_templates.error_warning("Det må være mer enn 2 i lobbyen")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
await self.end_lobby() await self.end_lobby()
@@ -136,7 +133,7 @@ class TeamLeaderView(discord.ui.View):
async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item): async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item):
self.bot.logger.error("TeamLeaderView error", exc_info=error) self.bot.logger.error("TeamLeaderView error", exc_info=error)
embed = embed_templates.error_warning(interaction, text="Oopsie woopsie, we made a fucky wucky!") embed = embed_templates.error_fatal("Oopsie woopsie, we made a fucky wucky!")
await interaction.response.send_message(embed=embed, delete_after=10) await interaction.response.send_message(embed=embed, delete_after=10)
@@ -151,7 +148,7 @@ class TeamLeaderSelectMenu(discord.ui.Select):
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
if interaction.user != self.parent_view.lobby.host: if interaction.user != self.parent_view.lobby.host:
embed = embed_templates.error_warning(interaction, text="Bare hosten kan velge lagledere") embed = embed_templates.error_warning("Bare hosten kan velge lagledere")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
team_leaders = [] team_leaders = []
@@ -203,7 +200,7 @@ class TeamSelectView(discord.ui.View):
async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item): async def on_error(self, interaction: discord.Interaction, error: Exception, item: discord.ui.Item):
self.bot.logger.error("TeamSelectView error", exc_info=error) self.bot.logger.error("TeamSelectView error", exc_info=error)
embed = embed_templates.error_warning(interaction, text="Oopsie woopsie, we made a fucky wucky") embed = embed_templates.error_warning("Oopsie woopsie, we made a fucky wucky")
await interaction.response.send_message(embed=embed, delete_after=10) await interaction.response.send_message(embed=embed, delete_after=10)
def teams_ready(self, interaction): def teams_ready(self, interaction):
@@ -246,11 +243,11 @@ class TeamSelectMenu(discord.ui.Select):
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
if interaction.user != self.parent_view.teams[0][0] and interaction.user != self.parent_view.teams[1][0]: if interaction.user != self.parent_view.teams[0][0] and interaction.user != self.parent_view.teams[1][0]:
embed = embed_templates.error_warning(interaction, text="Bare lagledere kan velge spillere") embed = embed_templates.error_warning("Bare lagledere kan velge spillere")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if interaction.user != self.parent_view.teams[self.parent_view.turn][0]: if interaction.user != self.parent_view.teams[self.parent_view.turn][0]:
embed = embed_templates.error_warning(interaction, text="Det er ikke din tur enda") embed = embed_templates.error_warning("Det er ikke din tur enda")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
turn_in_question = self.parent_view.turn turn_in_question = self.parent_view.turn
@@ -264,7 +261,7 @@ class TeamSelectMenu(discord.ui.Select):
await self.rerender_players(interaction, turn_in_question) await self.rerender_players(interaction, turn_in_question)
embed = embed_templates.success(interaction, text="Bruker valgt!") embed = embed_templates.success("Bruker valgt!")
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5) await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5)
@@ -279,7 +276,7 @@ class MoveTeamVoiceButton(discord.ui.Button):
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
if interaction.user != self.parent_view.teams[0][0] and interaction.user != self.parent_view.teams[1][0]: if interaction.user != self.parent_view.teams[0][0] and interaction.user != self.parent_view.teams[1][0]:
embed = embed_templates.error_warning(interaction, text="Bare lagleder kan flytte laget!") embed = embed_templates.error_warning("Bare lagleder kan flytte laget!")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if interaction.user == self.parent_view.teams[0][0]: if interaction.user == self.parent_view.teams[0][0]:
@@ -295,21 +292,20 @@ class MoveTeamVoiceButton(discord.ui.Button):
failed_users = await temp_voice.move_players(interaction, self.clicked[team["number"]], team["players"]) failed_users = await temp_voice.move_players(interaction, self.clicked[team["number"]], team["players"])
else: else:
embed = embed_templates.error_warning( embed = embed_templates.error_warning(
interaction, text=f"Kanal for lag {team['number']} allerede opprettet og spillere flyttet!" f"Kanal for lag {team['number']} allerede opprettet og spillere flyttet!"
) )
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if failed_users: if failed_users:
failed_users = "\n".join([f"* {user.mention}" for user in failed_users]) failed_users = "\n".join([f"* {user.mention}" for user in failed_users])
embed = embed_templates.error_warning( embed = embed_templates.error_warning(
interaction, f"Klarte ikke å flytte følgende brukere til kanalen:\n{failed_users}\n\n"
text=f"Klarte ikke å flytte følgende brukere til kanalen:\n{failed_users}\n\n"
+ "Dette kan være fordi brukere(ne) ikke er koblet til en kanal fra før" + "Dette kan være fordi brukere(ne) ikke er koblet til en kanal fra før"
+ "eller at jeg ikke har tillatelse til å flytte", + "eller at jeg ikke har tillatelse til å flytte",
) )
return await interaction.response.send_message(embed=embed, delete_after=10) return await interaction.response.send_message(embed=embed, delete_after=10)
embed = embed_templates.success(interaction, text=f"Flyttet lag {team['number']} til voice!") embed = embed_templates.success(f"Flyttet lag {team['number']} til voice!")
await interaction.response.send_message(embed=embed, delete_after=10) await interaction.response.send_message(embed=embed, delete_after=10)
-163
View File
@@ -1,163 +0,0 @@
import io
import traceback
import aiohttp
import discord
from discord import app_commands
from discord.ext import commands
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
from PIL import ImageSequence
from cogs.utils import embed_templates
class The(commands.Cog):
"""
This cog lets you make a "the" barnacle boy laser eyes meme
"""
def __init__(self, bot: commands.Bot):
self.bot = bot
self.background_url = "https://i.kym-cdn.com/photos/images/newsfeed/001/777/063/d42.png"
self.x = 30
self.y = 340
async def fetch_image(self, url: str) -> io.BytesIO:
"""
Fetch the image from the URL
Parameters
----------
url (str): url
Returns
----------
(io.BytesIO): data buffer
"""
if not url:
return None
async with aiohttp.ClientSession() as session:
async with session.get(url) as resp:
if resp.status != 200:
return None
data = io.BytesIO(await resp.read())
return data
def outline_text(
self,
draw: ImageDraw,
text: str,
font: ImageFont.FreeTypeFont,
x: int,
y: int,
thickness: int,
) -> None:
"""
Outlines given text
"""
for dx in range(-thickness, thickness + 1):
for dy in range(-thickness, thickness + 1):
if dx != 0 or dy != 0:
draw.text((x + dx, y + dy), text, font=font, fill="black")
draw.text((x, y), text, font=font, fill="white")
@app_commands.command()
async def the(
self, interaction: discord.Interaction, top_text: str = "", image_url: str = "", bottom_text: str = ""
):
"""
Generer en barnacle boy laser eyes THE mem basert på gitt bilde/tekst
Parameters
----------
interaction (discord.Interaction): Slash command context object
caption (str): top text
image_url (str): image url
bottom_text (str): bottom text
Returns
----------
(discord.File): an image
"""
await interaction.response.defer()
gif = False
try:
bg_data = await self.fetch_image(self.background_url)
background = Image.open(bg_data)
image_data = await self.fetch_image(image_url)
if image_data:
image = Image.open(image_data)
if image.format.lower() == "gif":
gif = True
frames = []
for frame in ImageSequence.Iterator(image):
frame = frame.convert("RGBA").resize((200, 200), Image.ANTIALIAS)
new_frame = background.copy().convert("RGBA")
new_frame.alpha_composite(frame, (self.x, self.y))
frames.append(new_frame)
draw = ImageDraw.Draw(frames[0], "RGBA")
else:
image = image.convert("RGBA").resize((200, 200), Image.ANTIALIAS)
background = background.convert("RGBA")
background.alpha_composite(image, (self.x, self.y))
draw = ImageDraw.Draw(background, "RGBA")
font = ImageFont.truetype("./src/assets/fonts/impact.ttf", 120)
if top_text:
text_x, text_y = (210, -10)
self.outline_text(draw, top_text.upper(), font, text_x, text_y, thickness=5)
if bottom_text:
text_x, text_y = (150, 550)
self.outline_text(draw, bottom_text.upper(), font, text_x, text_y, thickness=5)
if gif:
with io.BytesIO() as output:
frames[0].save(
output,
format="GIF",
save_all=True,
append_images=frames[1:],
duration=image.info["duration"],
loop=0,
transparency=image.info.get("transparency", 0),
disposal=2, # Use 'restore to background color' disposal method
)
output.seek(0)
await interaction.followup.send(file=discord.File(output, "result.gif"))
else:
with io.BytesIO() as output:
background.save(output, format="PNG")
output.seek(0)
await interaction.followup.send(file=discord.File(output, "result.png"))
except Exception as e:
self.bot.logger.warning(f"An error occurred while processing the images: {e}")
traceback.print_exc()
await interaction.followup.send(
embed=embed_templates.error_fatal(
interaction, "Klarte ikke å generere mem! Har du gitt en ordentlig bildelenke?"
)
)
async def setup(bot):
"""
Add the cog to the bot on extension load
Parameters
----------
bot (commands.Bot): Bot instance
"""
await bot.add_cog(The(bot))
+36 -28
View File
@@ -34,10 +34,12 @@ class UserFacts(commands.Cog):
"ESFP", "ESFP",
} }
self.mbti_list = list(self.mbti_codes) self.mbti_list = list(self.mbti_codes)
self.similarity_matrix = self._create_similarity_matrix() self.similarity_matrix = self.create_similarity_matrix()
def init_db(self): def init_db(self):
"""Create the necessary tables for the birthday cog to work""" """
Create the necessary tables for the birthday cog to work
"""
self.cursor.execute( self.cursor.execute(
""" """
@@ -51,6 +53,8 @@ class UserFacts(commands.Cog):
height_group = app_commands.Group(name="høyde", description="Se, endre eller fjern høyde for brukere på serveren") height_group = app_commands.Group(name="høyde", description="Se, endre eller fjern høyde for brukere på serveren")
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@height_group.command(name="se", description="Se høyden til en bruker") @height_group.command(name="se", description="Se høyden til en bruker")
async def height_see(self, interaction: discord.Interaction, bruker: discord.Member = None): async def height_see(self, interaction: discord.Interaction, bruker: discord.Member = None):
""" """
@@ -76,7 +80,7 @@ class UserFacts(commands.Cog):
if not height: if not height:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Brukeren har ikke lagt inn høyden sin") embed=embed_templates.error_warning("Brukeren har ikke lagt inn høyden sin")
) )
height = height[0] height = height[0]
@@ -92,6 +96,8 @@ class UserFacts(commands.Cog):
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@height_group.command(name="sett", description="Sett høyden din") @height_group.command(name="sett", description="Sett høyden din")
@app_commands.rename(height_cm="høyde_cm") @app_commands.rename(height_cm="høyde_cm")
async def height_set(self, interaction: discord.Interaction, height_cm: app_commands.Range[int, 50, 250]): async def height_set(self, interaction: discord.Interaction, height_cm: app_commands.Range[int, 50, 250]):
@@ -114,8 +120,10 @@ class UserFacts(commands.Cog):
(interaction.user.id, height_cm, height_cm), (interaction.user.id, height_cm, height_cm),
) )
await interaction.response.send_message(embed=embed_templates.success(interaction, text="Høyde satt!")) await interaction.response.send_message(embed=embed_templates.success("Høyde satt!"))
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@height_group.command(name="fjern", description="Fjern høyden din") @height_group.command(name="fjern", description="Fjern høyden din")
async def height_remove(self, interaction: discord.Interaction): async def height_remove(self, interaction: discord.Interaction):
""" """
@@ -137,13 +145,13 @@ class UserFacts(commands.Cog):
if self.cursor.rowcount == 0: if self.cursor.rowcount == 0:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.success( embed=embed_templates.success("Du hadde ikke lagt inn høyden din fra før av men ok :)")
interaction, text="Du hadde ikke lagt inn høyden din fra før av men ok :)"
)
) )
await interaction.response.send_message(embed=embed_templates.success(interaction, text="Høyde fjernet!")) await interaction.response.send_message(embed=embed_templates.success("Høyde fjernet!"))
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@height_group.command(name="leaderboard", description="Se en leaderboard over høyder") @height_group.command(name="leaderboard", description="Se en leaderboard over høyder")
async def height_leaderboard(self, interaction: discord.Interaction): async def height_leaderboard(self, interaction: discord.Interaction):
""" """
@@ -164,17 +172,12 @@ class UserFacts(commands.Cog):
if not (result := self.cursor.fetchall()): if not (result := self.cursor.fetchall()):
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Ingen har lagt inn høyden sin enda") embed=embed_templates.error_warning("Ingen har lagt inn høyden sin enda")
) )
result_formatted = list( results_formatted = [f"**#{i+1}** <@{row[0]}> - `{row[1]}` cm" for i, row in enumerate(result)]
map(
lambda s: f"**#{s[0]+1}** <@{s[1][0]}> - `{s[1][1]}` cm",
enumerate(result),
)
)
paginator = misc_utils.Paginator(result_formatted) paginator = misc_utils.Paginator(results_formatted)
view = discord_utils.Scroller(paginator, interaction.user) view = discord_utils.Scroller(paginator, interaction.user)
embed = view.construct_embed(discord.Embed(title="Våre høyeste og laveste (👑)")) embed = view.construct_embed(discord.Embed(title="Våre høyeste og laveste (👑)"))
@@ -182,6 +185,8 @@ class UserFacts(commands.Cog):
mbti_group = app_commands.Group(name="mbti", description="Se, endre eller fjern MBTI for brukere på serveren") mbti_group = app_commands.Group(name="mbti", description="Se, endre eller fjern MBTI for brukere på serveren")
@app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True)
@app_commands.checks.cooldown(1, 2)
@mbti_group.command(name="se", description="Se MBTI-en til en bruker") @mbti_group.command(name="se", description="Se MBTI-en til en bruker")
async def mbti_see(self, interaction: discord.Interaction, bruker: discord.Member = None): async def mbti_see(self, interaction: discord.Interaction, bruker: discord.Member = None):
""" """
@@ -207,7 +212,7 @@ class UserFacts(commands.Cog):
if not user_mbti: if not user_mbti:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Brukeren har ikke lagt inn MBTI-en sin") embed=embed_templates.error_warning("Brukeren har ikke lagt inn MBTI-en sin")
) )
user_mbti = user_mbti[0] user_mbti = user_mbti[0]
@@ -235,7 +240,7 @@ class UserFacts(commands.Cog):
else: else:
others.append((discord_id, mbti)) others.append((discord_id, mbti))
self._create_mbti_graph((bruker, user_mbti), others) self.create_mbti_graph((bruker, user_mbti), others)
with open(f"src/assets/temp/{bruker.id}_mbti.png", "rb") as f: with open(f"src/assets/temp/{bruker.id}_mbti.png", "rb") as f:
image = discord.File(f, filename=f"{bruker.id}_mbti.png") image = discord.File(f, filename=f"{bruker.id}_mbti.png")
@@ -243,6 +248,8 @@ class UserFacts(commands.Cog):
embed.set_image(url=f"attachment://{bruker.id}_mbti.png") embed.set_image(url=f"attachment://{bruker.id}_mbti.png")
await interaction.response.send_message(embed=embed, file=image) await interaction.response.send_message(embed=embed, file=image)
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@mbti_group.command(name="sett", description="Sett MBTI-en din") @mbti_group.command(name="sett", description="Sett MBTI-en din")
async def mbti_set(self, interaction: discord.Interaction, mbti: str): async def mbti_set(self, interaction: discord.Interaction, mbti: str):
""" """
@@ -255,9 +262,7 @@ class UserFacts(commands.Cog):
""" """
if mbti.upper() not in self.mbti_codes: if mbti.upper() not in self.mbti_codes:
return await interaction.response.send_message( return await interaction.response.send_message(embed=embed_templates.error_warning("Ugyldig MBTI"))
embed=embed_templates.error_warning(interaction, text="Ugyldig MBTI")
)
self.cursor.execute( self.cursor.execute(
""" """
@@ -269,7 +274,7 @@ class UserFacts(commands.Cog):
(interaction.user.id, mbti.upper(), mbti.upper()), (interaction.user.id, mbti.upper(), mbti.upper()),
) )
await interaction.response.send_message(embed=embed_templates.success(interaction, text="MBTI satt!")) await interaction.response.send_message(embed=embed_templates.success("MBTI satt!"))
@mbti_set.autocomplete("mbti") @mbti_set.autocomplete("mbti")
async def mbti_set_autocomplete_callback(self, interaction: discord.Interaction, current: str): async def mbti_set_autocomplete_callback(self, interaction: discord.Interaction, current: str):
@@ -286,6 +291,8 @@ class UserFacts(commands.Cog):
app_commands.Choice(name=mbti, value=mbti) for mbti in self.mbti_codes if mbti.startswith(current.upper()) app_commands.Choice(name=mbti, value=mbti) for mbti in self.mbti_codes if mbti.startswith(current.upper())
] ]
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@mbti_group.command(name="fjern", description="Fjern MBTI-en din") @mbti_group.command(name="fjern", description="Fjern MBTI-en din")
async def mbti_remove(self, interaction: discord.Interaction): async def mbti_remove(self, interaction: discord.Interaction):
""" """
@@ -307,13 +314,13 @@ class UserFacts(commands.Cog):
if self.cursor.rowcount == 0: if self.cursor.rowcount == 0:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.success( embed=embed_templates.success("Du hadde ikke lagt inn MBTIen din fra før av men ok :)")
interaction, text="Du hadde ikke lagt inn MBTIen din fra før av men ok :)"
)
) )
await interaction.response.send_message(embed=embed_templates.success(interaction, text="MBTI fjernet!")) await interaction.response.send_message(embed=embed_templates.success("MBTI fjernet!"))
@app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@mbti_group.command(name="forklaring", description="Skjønner du ikke hva MBTI er? Her er en forklaring") @mbti_group.command(name="forklaring", description="Skjønner du ikke hva MBTI er? Her er en forklaring")
async def mbti_explanation(self, interaction: discord.Interaction): async def mbti_explanation(self, interaction: discord.Interaction):
""" """
@@ -334,7 +341,7 @@ class UserFacts(commands.Cog):
) )
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
def _create_similarity_matrix(self): def create_similarity_matrix(self):
def similarity(mbti_1, mbti_2): def similarity(mbti_1, mbti_2):
similarity = 0 similarity = 0
for i in range(4): for i in range(4):
@@ -352,7 +359,8 @@ class UserFacts(commands.Cog):
return similarity_matrix return similarity_matrix
def _create_mbti_graph(self, user_mbti: tuple[discord.Member, str], others: list[tuple[discord.Member, str]]): # TODO: fix this. The distances are wrong
def create_mbti_graph(self, user_mbti: tuple[discord.Member, str], others: list[tuple[discord.Member, str]]):
user, mbti = user_mbti user, mbti = user_mbti
# Create a Graphviz graph # Create a Graphviz graph
+34 -43
View File
@@ -1,3 +1,4 @@
import os
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime
from io import BytesIO from io import BytesIO
@@ -10,40 +11,30 @@ from . import embed_templates
from .misc_utils import Paginator from .misc_utils import Paginator
def get_color(discord_object: discord.User | discord.Member | discord.Role) -> discord.Color: async def send_as_txt_file(interaction: discord.Interaction, content: str, file_path: str):
""" """
Returns the user's top role color Sends a string as a txt file and deletes the file afterwards
Parameters Parameters
----------- ----------
discord_object (discord.User|discord.Member|discord.Role): A discord object that has a color attribute interaction (discord.Interaction): Slash command context object
content (str): String that's too long to send
Returns file_path (str): Path to file
-----------
(discord.Color): The user's displayed color
""" """
if hasattr(discord_object, "color") and str(discord_object.color) != "#000000": # Create file
return discord_object.color with open(file_path, "w", encoding="utf-8") as file:
file.write(content)
return discord.Colour(0x99AAB5) # Send file
txt_file = discord.File(file_path)
await interaction.response.send_message(file=txt_file)
# Delete file
async def sleep_until_midnight(bot): try:
"""Syncs loop with the time of day""" os.remove(file_path)
except OSError:
await bot.wait_until_ready() pass
now = datetime.datetime.now()
if now.hour > 0:
sleep_until = now + datetime.timedelta(days=1)
sleep_until = sleep_until.replace(hour=0, minute=0, second=0, microsecond=0)
else:
sleep_until = now.replace(hour=0, minute=0, second=0, microsecond=0)
bot.logger.info(f"Loop sleeping until {sleep_until}")
await discord.utils.sleep_until(sleep_until)
async def get_file_bytesio(file: discord.Attachment | discord.Asset) -> BytesIO: async def get_file_bytesio(file: discord.Attachment | discord.Asset) -> BytesIO:
@@ -240,37 +231,37 @@ class LobbyView(discord.ui.View):
@discord.ui.button(label="Bli med", style=discord.ButtonStyle.blurple) @discord.ui.button(label="Bli med", style=discord.ButtonStyle.blurple)
async def join_lobby(self, interaction: discord.Interaction, button: discord.ui.Button): async def join_lobby(self, interaction: discord.Interaction, button: discord.ui.Button):
if datetime.now() > self.lobby.ends: if datetime.now() > self.lobby.ends:
embed = embed_templates.error_warning(interaction, text="Lobbyen har allerede startet") embed = embed_templates.error_warning("Lobbyen har allerede startet")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if ( if (
interaction.user.id in self.lobby.kicked_players interaction.user.id in self.lobby.kicked_players
): # We have to use the ID here to avoid fetching the member object in the selection menu ): # We have to use the ID here to avoid fetching the member object in the selection menu
embed = embed_templates.error_warning(interaction, text="Du er blitt kastet ut av lobbyen") embed = embed_templates.error_warning("Du er blitt kastet ut av lobbyen")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if interaction.user in self.lobby.players: if interaction.user in self.lobby.players:
embed = embed_templates.error_warning(interaction, text="Du er allerede i lobbyen") embed = embed_templates.error_warning("Du er allerede i lobbyen")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if len(self.lobby.players) >= 10: if len(self.lobby.players) >= 10:
embed = embed_templates.error_warning(interaction, text="Lobbyen er full") embed = embed_templates.error_warning("Lobbyen er full")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
self.lobby.players.append(interaction.user) self.lobby.players.append(interaction.user)
await self.rerender_players(interaction) await self.rerender_players(interaction)
embed = embed_templates.success(interaction, text="Du har blitt med i lobbyen") embed = embed_templates.success("Du har blitt med i lobbyen")
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5) await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5)
@discord.ui.button(label="Start", style=discord.ButtonStyle.green) @discord.ui.button(label="Start", style=discord.ButtonStyle.green)
async def start_lobby(self, interaction: discord.Interaction, button: discord.ui.Button): async def start_lobby(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user != self.lobby.host: if interaction.user != self.lobby.host:
embed = embed_templates.error_warning(interaction, text="Bare hosten kan starte") embed = embed_templates.error_warning("Bare hosten kan starte")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if len(self.lobby.players) < 3: if len(self.lobby.players) < 3:
embed = embed_templates.error_warning(interaction, text="Det må være mer enn 2 i lobbyen") embed = embed_templates.error_warning("Det må være mer enn 2 i lobbyen")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
await self.end_lobby() await self.end_lobby()
@@ -283,30 +274,30 @@ class LobbyView(discord.ui.View):
async def leave_lobby(self, interaction: discord.Interaction, button: discord.ui.Button): async def leave_lobby(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user == self.lobby.host: if interaction.user == self.lobby.host:
embed = embed_templates.error_warning( embed = embed_templates.error_warning(
interaction, text="Du kan ikke forlate lobbyen som host. Slett lobbyen i stedet om ønskelig!" "Du kan ikke forlate lobbyen som host. Slett lobbyen i stedet om ønskelig!"
) )
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if interaction.user not in self.lobby.players: if interaction.user not in self.lobby.players:
embed = embed_templates.error_warning(interaction, text="Du er ikke i lobbyen") embed = embed_templates.error_warning("Du er ikke i lobbyen")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
self.lobby.players.remove(interaction.user) self.lobby.players.remove(interaction.user)
await self.rerender_players(interaction) await self.rerender_players(interaction)
embed = embed_templates.success(interaction, text="Du har forlatt lobbyen") embed = embed_templates.success("Du har forlatt lobbyen")
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5) await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5)
@discord.ui.button(label="Slett", style=discord.ButtonStyle.red) @discord.ui.button(label="Slett", style=discord.ButtonStyle.red)
async def delete_lobby(self, interaction: discord.Interaction, button: discord.ui.Button): async def delete_lobby(self, interaction: discord.Interaction, button: discord.ui.Button):
if interaction.user != self.lobby.host: if interaction.user != self.lobby.host:
embed = embed_templates.error_warning(interaction, text="Bare hosten kan slette lobbyen") embed = embed_templates.error_warning("Bare hosten kan slette lobbyen")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
self.lobby.ends = datetime.now() self.lobby.ends = datetime.now()
await interaction.message.delete() await interaction.message.delete()
embed = embed_templates.success(interaction, text="Lobbyen har blitt slettet") embed = embed_templates.success("Lobbyen har blitt slettet")
await interaction.response.send_message(embed=embed, delete_after=5) await interaction.response.send_message(embed=embed, delete_after=5)
@@ -332,11 +323,11 @@ class _KickSelectMenu(discord.ui.Select):
async def callback(self, interaction: discord.Interaction): async def callback(self, interaction: discord.Interaction):
if interaction.user != self.parent_view.lobby.host: if interaction.user != self.parent_view.lobby.host:
embed = embed_templates.error_warning(interaction, text="Bare hosten kan kicke spillere") embed = embed_templates.error_warning("Bare hosten kan kicke spillere")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
if str(interaction.user.id) in interaction.data["values"][0]: if str(interaction.user.id) in interaction.data["values"][0]:
embed = embed_templates.error_warning(interaction, text="Du kan ikke kicke deg selv") embed = embed_templates.error_warning("Du kan ikke kicke deg selv")
return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10) return await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=10)
self.parent_view.lobby.kicked_players.append(int(interaction.data["values"][0])) self.parent_view.lobby.kicked_players.append(int(interaction.data["values"][0]))
@@ -352,7 +343,7 @@ class _KickSelectMenu(discord.ui.Select):
await self.rerender_players(interaction) await self.rerender_players(interaction)
embed = embed_templates.success(interaction, text="Brukeren har blitt sparket fra lobbyen") embed = embed_templates.success("Brukeren har blitt sparket fra lobbyen")
await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5) await interaction.response.send_message(embed=embed, ephemeral=True, delete_after=5)
@@ -438,7 +429,7 @@ class TempVoiceHelper:
except discord.Forbidden: except discord.Forbidden:
self.bot.logger.error(f"Failed to create temporary voice channel in {interaction.guild}") self.bot.logger.error(f"Failed to create temporary voice channel in {interaction.guild}")
await interaction.response.send_message( await interaction.response.send_message(
embed=embed_templates.error_fatal(interaction, text="Jeg har ikke tilgang til å opprette en talekanal") embed=embed_templates.error_fatal("Jeg har ikke tilgang til å opprette en talekanal")
) )
return return
+3 -29
View File
@@ -1,30 +1,12 @@
import discord import discord
def default_footer(interaction: discord.Interaction, embed: discord.Embed) -> discord.Embed: def error_warning(text: str) -> discord.Embed:
"""
Sets a footer containing the command invoker's name, discriminator and avatar
Parameters
-----------
interaction (discord.Interaction): Slash command context object
embed (discord.Embed): An embed object to add the footer to
Returns
-----------
(discord.Embed): The passed in embed + footer
"""
return embed.set_footer(icon_url=interaction.user.avatar, text=interaction.user.name)
def error_warning(interaction: discord.Interaction, text: str) -> discord.Embed:
""" """
Creates an embed with a specified error message based on a warning template Creates an embed with a specified error message based on a warning template
Parameters Parameters
----------- -----------
interaction (discord.Interaction): Slash command context object
text (str): The error message text (str): The error message
Returns Returns
@@ -33,18 +15,15 @@ def error_warning(interaction: discord.Interaction, text: str) -> discord.Embed:
""" """
embed = discord.Embed(color=discord.Color.gold(), description=f"⚠️ {text}") embed = discord.Embed(color=discord.Color.gold(), description=f"⚠️ {text}")
default_footer(interaction, embed)
return embed return embed
def error_fatal(interaction: discord.Interaction, text: str) -> discord.Embed: def error_fatal(text: str) -> discord.Embed:
""" """
Creates an embed with a specified error message based on a warning template Creates an embed with a specified error message based on a warning template
Parameters Parameters
----------- -----------
interaction (discord.Interaction): Slash command context object
text (str): The error message text (str): The error message
Returns Returns
@@ -53,18 +32,15 @@ def error_fatal(interaction: discord.Interaction, text: str) -> discord.Embed:
""" """
embed = discord.Embed(color=discord.Color.red(), description=f"❌ {text}") embed = discord.Embed(color=discord.Color.red(), description=f"❌ {text}")
default_footer(interaction, embed)
return embed return embed
def success(interaction: discord.Interaction, text: str) -> discord.Embed: def success(text: str) -> discord.Embed:
""" """
Creates an embed with a specified message using a template signifying success. Creates an embed with a specified message using a template signifying success.
Parameters Parameters
----------- -----------
interaction (discord.Interaction): Slash command context object
text (str): The message text (str): The message
Returns Returns
@@ -73,6 +49,4 @@ def success(interaction: discord.Interaction, text: str) -> discord.Embed:
""" """
embed = discord.Embed(color=discord.Color.green(), description=f"✅ {text}") embed = discord.Embed(color=discord.Color.green(), description=f"✅ {text}")
default_footer(interaction, embed)
return embed return embed
+94
View File
@@ -1,8 +1,27 @@
import datetime
import textwrap
from math import ceil from math import ceil
from zoneinfo import ZoneInfo
import cv2
import numpy as np
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFont
MIDNIGHT = datetime.time(hour=19, minute=48, tzinfo=ZoneInfo("Europe/Oslo"))
class Paginator: class Paginator:
"""Interface for managing content. Divides your content into pages of 10 items each"""
def __init__(self, content: list): def __init__(self, content: list):
"""
Parameters
----------
content (list): The content you want to paginate
"""
self.content = content self.content = content
self.total_page_count = ceil(len(content) / 10) self.total_page_count = ceil(len(content) / 10)
self.current_page = 1 self.current_page = 1
@@ -19,6 +38,7 @@ class Paginator:
---------- ----------
(list|None): The page you requested (list|None): The page you requested
""" """
if page < 1 or page > self.total_page_count: if page < 1 or page > self.total_page_count:
return None return None
@@ -35,6 +55,7 @@ class Paginator:
---------- ----------
(list): The current page of the paginator (list): The current page of the paginator
""" """
return self.get_page(self.current_page) return self.get_page(self.current_page)
def next_page(self) -> list | None: def next_page(self) -> list | None:
@@ -45,6 +66,7 @@ class Paginator:
---------- ----------
(list): The next page of the paginator (list): The next page of the paginator
""" """
self.current_page += 1 self.current_page += 1
return self.get_page(self.current_page) return self.get_page(self.current_page)
@@ -56,6 +78,7 @@ class Paginator:
---------- ----------
(list): The previous page of the paginator (list): The previous page of the paginator
""" """
self.current_page -= 1 self.current_page -= 1
return self.get_page(self.current_page) return self.get_page(self.current_page)
@@ -67,6 +90,7 @@ class Paginator:
---------- ----------
(list): The first page of the paginator (list): The first page of the paginator
""" """
self.current_page = 1 self.current_page = 1
return self.get_page(self.current_page) return self.get_page(self.current_page)
@@ -78,5 +102,75 @@ class Paginator:
---------- ----------
(list): The last page of the paginator (list): The last page of the paginator
""" """
self.current_page = self.total_page_count self.current_page = self.total_page_count
return self.get_page(self.current_page) return self.get_page(self.current_page)
async def put_text_in_box(
image: np.ndarray,
text: str,
top_left: tuple,
bottom_right: tuple,
font_path: str = "./src/assets/fonts/impact.ttf",
font_size: int = 10,
color: tuple = (0, 0, 0),
):
"""
Puts text inside a bounding box on the cv2 image with automatic text wrapping, using PIL for text rendering.
Parameters
----------
image (np.ndarray): Source cv2 image.
text (str): Text to be put on the image.
top_left (tuple): (x, y) Top-left corner of the bounding box.
bottom_right (tuple): (x, y) Bottom-right corner of the bounding box.
font_path (str): Path to the font file. Defaults to Impact font.
font_size (int): Initial font size. Defaults to 10.
color (tuple): Text color in RGB. Defaults to (0, 0, 0).
"""
# Convert the cv2 image to a PIL image
image_pil = Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(image_pil)
# Get coordinates of the bounding box
x1, y1 = top_left
x2, y2 = bottom_right
# Get the width and height of the bounding box
# We multiply by 0.9 to give some padding
w, h = (x2 - x1) * 0.9, (y2 - y1) * 0.9
# Initialize font width/height to 0 in order to run the while loops below at least once
font_width = 0
font_height = 0
# Add newlines to the text. Here we have gone with 15 characters per line
# This is arbitrary, but seems to work well
text = textwrap.fill(text, width=15)
# Increase the font size until the longest line of text does not fit in the bounding box anymore
# We also have to check the height, since the text might be too tall for the box
while font_width < w and font_height < h:
font = ImageFont.truetype(font_path, font_size)
longest_line = max(text.split("\n"), key=lambda x: font.getlength(x))
font_width = font.getlength(longest_line)
# bbox doesn't work with multiline text, so we have to calculate the height manually
# take the height of the bounding box, multiply by 1.5 (our assumed line dividor height)
# after that, we multiply by the number of lines
font_box = font.getbbox(text)
font_height = ((font_box[3] - font_box[1]) * 1.5) * text.count("\n")
font_size += 1
# Actually draw the text
draw.multiline_textbbox((w, h), text=text, font=font, align="center", anchor="mm")
center_of_box = ((x1 + x2) // 2, (y1 + y2) // 2)
draw.multiline_text(center_of_box, text, font=font, fill=color, align="center", anchor="mm")
# Convert back to cv2 image format and update the original image
cv2_image = cv2.cvtColor(np.array(image_pil), cv2.COLOR_RGB2BGR)
np.copyto(image, cv2_image)
+9 -19
View File
@@ -53,9 +53,8 @@ async def fetch_article(title: str):
# This is wildly inefficent but I don't. fucking. care. Just make it work # This is wildly inefficent but I don't. fucking. care. Just make it work
text = regex.sub( text = regex.sub(
r"(?=\{)(\{([^{}]|(?1))*\})|\[\[(Kategori|Fil):.+?\]\]|\{.+?\}", "", text, flags=regex.MULTILINE | regex.DOTALL r"(?=\{)(\{([^{}]|(?1))*\})|\[\[(Kategori|Fil):.+?\]\]|\{.+?\}", "", text, flags=regex.MULTILINE | regex.DOTALL
) # Remove templates and categories ) # Remove templates, categories and images
text = pypandoc.convert_text(text, "markdown", format="mediawiki") text = pypandoc.convert_text(text, "markdown", format="mediawiki")
print(text)
text = regex.sub( text = regex.sub(
r"\[(.+?)\]\(.+? \"wikilink\"\)", r"\1", text, flags=regex.MULTILINE | regex.DOTALL r"\[(.+?)\]\(.+? \"wikilink\"\)", r"\1", text, flags=regex.MULTILINE | regex.DOTALL
) # Pandoc adds alt text to links. discord doesn't support that ) # Pandoc adds alt text to links. discord doesn't support that
@@ -107,7 +106,7 @@ class Viteboka(commands.Cog):
viteboka_group = app_commands.Group(name="viteboka", description="Søk i Viteboka etter informasjon") viteboka_group = app_commands.Group(name="viteboka", description="Søk i Viteboka etter informasjon")
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True)
@app_commands.checks.cooldown(1, 10) @app_commands.checks.cooldown(1, 10)
@viteboka_group.command(name="søk", description="Søk etter en artikkel i Viteboka") @viteboka_group.command(name="søk", description="Søk etter en artikkel i Viteboka")
async def search(self, interaction: discord.Interaction, søkestreng: str): async def search(self, interaction: discord.Interaction, søkestreng: str):
@@ -131,7 +130,7 @@ class Viteboka(commands.Cog):
response = requests.get(url=API_URL, params=params) response = requests.get(url=API_URL, params=params)
if response.status_code != 200: if response.status_code != 200:
self.bot.logger.error(f"Failed to search for articles with query {søkestreng}: {response.status_code}") self.bot.logger.error(f"Failed to search for articles with query {søkestreng}: {response.status_code}")
embed = embed_templates.error_fatal(interaction, text="Klarte ikke å søke etter artikler") embed = embed_templates.error_fatal("Klarte ikke å søke etter artikler")
return await interaction.followup.send(embed=embed) return await interaction.followup.send(embed=embed)
data = response.json() data = response.json()
@@ -139,7 +138,7 @@ class Viteboka(commands.Cog):
if not search_results: if not search_results:
self.bot.logger.info(f"No articles found with query {søkestreng}") self.bot.logger.info(f"No articles found with query {søkestreng}")
embed = embed_templates.error_fatal(interaction, text="Fant ingen artikler som matcher søket") embed = embed_templates.error_warning("Fant ingen artikler som matcher søket")
return await interaction.followup.send(embed=embed) return await interaction.followup.send(embed=embed)
if len(search_results) == 1: if len(search_results) == 1:
@@ -160,16 +159,13 @@ class Viteboka(commands.Cog):
title, url, text, image = await fetch_article(søkestreng) title, url, text, image = await fetch_article(søkestreng)
except VitebokaException as e: except VitebokaException as e:
self.bot.logger.error(f"Failed to fetch article {søkestreng}: {e}") self.bot.logger.error(f"Failed to fetch article {søkestreng}: {e}")
embed = embed_templates.error_fatal( embed = embed_templates.error_fatal(str(e))
interaction,
text=str(e),
)
return await interaction.followup.send(embed=embed) return await interaction.followup.send(embed=embed)
embed = viteboka_embed(title, url, text, image) embed = viteboka_embed(title, url, text, image)
await interaction.followup.send(embed=embed) await interaction.followup.send(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True)
@app_commands.checks.cooldown(1, 5) @app_commands.checks.cooldown(1, 5)
@viteboka_group.command(name="tilfeldig", description="Få en tilfeldig artikkel fra Viteboka") @viteboka_group.command(name="tilfeldig", description="Få en tilfeldig artikkel fra Viteboka")
async def random(self, interaction: discord.Interaction): async def random(self, interaction: discord.Interaction):
@@ -194,7 +190,7 @@ class Viteboka(commands.Cog):
response = requests.get(url=API_URL, params=params) response = requests.get(url=API_URL, params=params)
if response.status_code != 200: if response.status_code != 200:
self.bot.logger.error(f"Failed to fetch random article: {response.status_code}") self.bot.logger.error(f"Failed to fetch random article: {response.status_code}")
embed = embed_templates.error_fatal(interaction, text="Klarte ikke å søke etter en tilfeldig artikkel") embed = embed_templates.error_fatal("Klarte ikke å søke etter en tilfeldig artikkel")
return await interaction.followup.send(embed=embed) return await interaction.followup.send(embed=embed)
data = response.json() data = response.json()
@@ -204,10 +200,7 @@ class Viteboka(commands.Cog):
title, url, text, image = await fetch_article(random_article_title) title, url, text, image = await fetch_article(random_article_title)
except VitebokaException as e: except VitebokaException as e:
self.bot.logger.error(f"Failed to fetch article {random_article_title}: {e}") self.bot.logger.error(f"Failed to fetch article {random_article_title}: {e}")
embed = embed_templates.error_fatal( embed = embed_templates.error_fatal(str(e))
interaction,
text=str(e),
)
return await interaction.followup.send(embed=embed) return await interaction.followup.send(embed=embed)
embed = viteboka_embed(title, url, text, image) embed = viteboka_embed(title, url, text, image)
@@ -254,10 +247,7 @@ class ArticleButton(discord.ui.Button):
try: try:
title, url, text, image = await fetch_article(self.article_title) title, url, text, image = await fetch_article(self.article_title)
except VitebokaException as e: except VitebokaException as e:
embed = embed_templates.error_fatal( embed = embed_templates.error_fatal(str(e))
interaction,
text=str(e),
)
return await interaction.followup.send(embed=embed) return await interaction.followup.send(embed=embed)
else: else:
embed = viteboka_embed(title, url, text, image) embed = viteboka_embed(title, url, text, image)
+7 -6
View File
@@ -8,7 +8,7 @@ from discord.ext import commands
class WebsiteEvents(commands.Cog): class WebsiteEvents(commands.Cog):
"""Miscellaneous commands that don"t fit anywhere else""" """Uploads Discord events to Sanity CMS"""
def __init__(self, bot: commands.Bot): def __init__(self, bot: commands.Bot):
""" """
@@ -57,6 +57,7 @@ class WebsiteEvents(commands.Cog):
self.bot.logger.info("Finished syncing events! Note errors may have occured") self.bot.logger.info("Finished syncing events! Note errors may have occured")
@commands.Cog.listener("on_scheduled_event_create")
async def create_event(self, event: discord.ScheduledEvent): async def create_event(self, event: discord.ScheduledEvent):
""" """
Creates an event in Sanity CMS when an event is added on Discord Creates an event in Sanity CMS when an event is added on Discord
@@ -67,8 +68,9 @@ class WebsiteEvents(commands.Cog):
""" """
# Convert timezone to Europe/Oslo # Convert timezone to Europe/Oslo
time = event.start_time.replace(tzinfo=pytz.utc) # Make sure the datetime object contains the timezone info # We need to set a timezone unit before being able to convert it to another timezone
time = event.start_time.astimezone(pytz.timezone("Europe/Oslo")) # That's why this line is so fucking weird
time = event.start_time.replace(tzinfo=pytz.utc).astimezone(pytz.timezone("Europe/Oslo"))
document = { document = {
"_type": "event", "_type": "event",
@@ -94,6 +96,7 @@ class WebsiteEvents(commands.Cog):
else: else:
self.bot.logger.error(f"Failed to create event in Sanity with ID: {event.id}. Response: {response.text}") self.bot.logger.error(f"Failed to create event in Sanity with ID: {event.id}. Response: {response.text}")
@commands.Cog.listener("on_scheduled_event_delete")
async def delete_event(self, event: discord.ScheduledEvent): async def delete_event(self, event: discord.ScheduledEvent):
""" """
Deletes an event in Sanity CMS when an event is deleted on Discord Deletes an event in Sanity CMS when an event is deleted on Discord
@@ -111,6 +114,7 @@ class WebsiteEvents(commands.Cog):
else: else:
self.bot.logger.error(f"Failed to delete event in Sanity with ID: {event.id}. Response: {response.text}") self.bot.logger.error(f"Failed to delete event in Sanity with ID: {event.id}. Response: {response.text}")
@commands.Cog.listener("on_scheduled_event_update")
async def update_event(self, before: discord.ScheduledEvent, after: discord.ScheduledEvent): async def update_event(self, before: discord.ScheduledEvent, after: discord.ScheduledEvent):
""" """
Updates an event in Sanity when edited on discord Updates an event in Sanity when edited on discord
@@ -141,7 +145,4 @@ async def setup(bot: commands.Bot):
bot (commands.Bot): Bot instance bot (commands.Bot): Bot instance
""" """
bot.add_listener(WebsiteEvents(bot).create_event, "on_scheduled_event_create")
bot.add_listener(WebsiteEvents(bot).update_event, "on_scheduled_event_update")
bot.add_listener(WebsiteEvents(bot).delete_event, "on_scheduled_event_delete")
await bot.add_cog(WebsiteEvents(bot)) await bot.add_cog(WebsiteEvents(bot))
+91 -54
View File
@@ -20,8 +20,6 @@ from wordcloud import WordCloud as WCloud # Avoid naming conflicts with cog cla
from cogs.utils import embed_templates from cogs.utils import embed_templates
# from wordcloud import ImageColorGenerator
class WordCloud(commands.Cog): class WordCloud(commands.Cog):
"""Generate a wordcloud based on the most frequent words posted""" """Generate a wordcloud based on the most frequent words posted"""
@@ -41,12 +39,16 @@ class WordCloud(commands.Cog):
# The user's defaultdict has a default value of 0 # The user's defaultdict has a default value of 0
self.word_freq_cache = defaultdict(lambda: defaultdict(int)) self.word_freq_cache = defaultdict(lambda: defaultdict(int))
# We cache the consenting users to avoid querying the database every 16:36
# It's frail but it works unless you have a skill issue
self.consenting_users = [] self.consenting_users = []
self.populate_consenting_users() self.populate_consenting_users()
nltk.download("stopwords") nltk.download("stopwords")
self.batch_update_word_freqs_loop.start() self.insert_cache_loop.start()
self.MSG_NO_DATA = "Fant ingen data om deg" # SonarCloud recommended this LMAO. Makes sense but also not
def init_db(self): def init_db(self):
""" """
@@ -78,8 +80,8 @@ class WordCloud(commands.Cog):
Insert cache to db, stop tasks and close the database connection on cog unload Insert cache to db, stop tasks and close the database connection on cog unload
""" """
await self.batch_update_word_freqs() self.bot.logger.info("Unloading cog")
self.batch_update_word_freqs_loop.cancel() self.insert_cache_loop.cancel()
self.cursor.close() self.cursor.close()
def populate_consenting_users(self): def populate_consenting_users(self):
@@ -95,7 +97,7 @@ class WordCloud(commands.Cog):
) )
self.consenting_users = [row[0] for row in self.cursor.fetchall()] self.consenting_users = [row[0] for row in self.cursor.fetchall()]
async def batch_update_word_freqs(self): async def insert_cache(self):
""" """
Inserts cached word frequencies into the database. Inserts cached word frequencies into the database.
We do this in order to prevent excess database writes We do this in order to prevent excess database writes
@@ -126,20 +128,25 @@ class WordCloud(commands.Cog):
word_freqs, word_freqs,
) )
except psycopg2.Error as err: except psycopg2.Error as err:
self.bot.db_connection.rollback()
self.bot.logger.error(f"Failed to insert wordcloud cache into database - {err}") self.bot.logger.error(f"Failed to insert wordcloud cache into database - {err}")
self.word_freq_cache.clear() self.word_freq_cache.clear()
@tasks.loop(minutes=20) @tasks.loop(minutes=20)
async def batch_update_word_freqs_loop(self): async def insert_cache_loop(self):
""" """
Clears and inserts word frequency cache into the database every 20 minutes Clears and inserts word frequency cache into the database every 20 minutes
""" """
self.bot.logger.info("Dumping word cloud cache to database...") self.bot.logger.info("Dumping word cloud cache to database...")
await self.batch_update_word_freqs() await self.insert_cache()
@insert_cache_loop.after_loop
async def on_insert_cache_loop_cancel(self):
if self.insert_cache_loop.is_being_cancelled() and self.word_freq_cache:
await self.insert_cache()
@commands.Cog.listener("on_message")
async def word_freq_listener(self, message: discord.Message): async def word_freq_listener(self, message: discord.Message):
""" """
Listens for all messages of consenting users Listens for all messages of consenting users
@@ -149,17 +156,52 @@ class WordCloud(commands.Cog):
message (discord.Message): Message to count message (discord.Message): Message to count
""" """
if message.author.id not in self.consenting_users: if not self.can_count_message(message):
return return
# Ignore bot commands tokens = self.tokenize(message.clean_content)
# This is a very naive approach but it works for our use case
if not message.clean_content[:2].isalpha(): for token in tokens:
return # Enter into cache
# This may take a performance hit but who tf cares. We're using python anyway
self.word_freq_cache[message.author.id][token] += 1
def can_count_message(self, message: discord.Message) -> bool:
"""
Check if a message can be counted
Parameters
----------
message (discord.Message): Message to check
Returns
----------
bool: Whether the message can be counted
"""
return (
message.author.id in self.consenting_users
and not message.clean_content[:2].isalpha() # Naive check for traditional bot commands
)
def tokenize(self, text: str) -> list[str]:
"""
Tokenize a text into words
Parameters
----------
text (str): Text to tokenize
Returns
----------
list[str]: List of words
"""
tokens = []
# Divide into words # Divide into words
# Also very naive but luckily we only speak norwegian and english :) # Very naive but luckily we only speak norwegian and english :)
words = message.clean_content.split(" ") words = text.split(" ")
for word in words: for word in words:
# Filter urls # Filter urls
@@ -172,9 +214,9 @@ class WordCloud(commands.Cog):
if not word: if not word:
continue continue
# Enter into cache tokens.append(word)
# This may take a performance hit but who tf cares. We're using python anyway
self.word_freq_cache[message.author.id][word] += 1 return tokens
@staticmethod @staticmethod
def generate_wordcloud(text: str, max_words: int = 4000, allow_bigrams: bool = False) -> BytesIO: def generate_wordcloud(text: str, max_words: int = 4000, allow_bigrams: bool = False) -> BytesIO:
@@ -193,7 +235,7 @@ class WordCloud(commands.Cog):
""" """
filter_words = set(stopwords.words("norwegian") + stopwords.words("english")) filter_words = set(stopwords.words("norwegian") + stopwords.words("english"))
mask = np.array(Image.open("./src/assets/word_cloud/mask.png")) mask = np.array(Image.open("./src/assets/word_cloud_mask.png"))
wc = WCloud( wc = WCloud(
max_words=max_words, max_words=max_words,
@@ -223,6 +265,7 @@ class WordCloud(commands.Cog):
) )
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@wordcloud_group.command(name="samtykke", description="Gi samtykke til å samle meldingsdataen din for ordskyer") @wordcloud_group.command(name="samtykke", description="Gi samtykke til å samle meldingsdataen din for ordskyer")
async def consent(self, interaction: discord.Interaction): async def consent(self, interaction: discord.Interaction):
""" """
@@ -235,7 +278,7 @@ class WordCloud(commands.Cog):
if interaction.user.id in self.consenting_users: if interaction.user.id in self.consenting_users:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Du har allerede samtykket"), ephemeral=False embed=embed_templates.error_warning("Du har allerede samtykket"), ephemeral=False
) )
try: try:
@@ -250,7 +293,7 @@ class WordCloud(commands.Cog):
self.bot.db_connection.rollback() self.bot.db_connection.rollback()
self.bot.logger.error(f"Failed to insert wordcloud metadata into database - {err}") self.bot.logger.error(f"Failed to insert wordcloud metadata into database - {err}")
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Klarte ikke å skrive til database"), embed=embed_templates.error_fatal("Klarte ikke å skrive til database"),
ephemeral=False, ephemeral=False,
) )
@@ -260,6 +303,7 @@ class WordCloud(commands.Cog):
await interaction.response.send_message(embed=embed, ephemeral=False) await interaction.response.send_message(embed=embed, ephemeral=False)
@app_commands.checks.bot_has_permissions(embed_links=True) @app_commands.checks.bot_has_permissions(embed_links=True)
@app_commands.checks.cooldown(1, 2)
@wordcloud_group.command(name="slett", description="Fjern samtykke og slett alle dine ordskydata") @wordcloud_group.command(name="slett", description="Fjern samtykke og slett alle dine ordskydata")
async def consent_remove(self, interaction: discord.Interaction): async def consent_remove(self, interaction: discord.Interaction):
""" """
@@ -274,7 +318,7 @@ class WordCloud(commands.Cog):
self.consenting_users.remove(interaction.user.id) self.consenting_users.remove(interaction.user.id)
except ValueError: except ValueError:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Fant ingen data om deg"), ephemeral=False embed=embed_templates.error_warning(self.MSG_NO_DATA), ephemeral=False
) )
self.word_freq_cache.pop(f"{interaction.user.id}", None) self.word_freq_cache.pop(f"{interaction.user.id}", None)
@@ -291,11 +335,11 @@ class WordCloud(commands.Cog):
self.bot.db_connection.rollback() self.bot.db_connection.rollback()
self.bot.logger.error(f"Failed to delete wordcloud metadata from database - {err}") self.bot.logger.error(f"Failed to delete wordcloud metadata from database - {err}")
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Klarte ikke å slette fra database"), embed=embed_templates.error_fatal("Klarte ikke å slette fra database"),
ephemeral=False, ephemeral=False,
) )
embed = embed_templates.success(interaction, "Meldingsdata er slettet!") embed = embed_templates.success("Meldingsdata er slettet!")
await interaction.response.send_message(embed=embed) await interaction.response.send_message(embed=embed)
@app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True) @app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True)
@@ -323,16 +367,18 @@ class WordCloud(commands.Cog):
if not result: if not result:
return await interaction.response.send_message( return await interaction.response.send_message(
embed=embed_templates.error_warning(interaction, text="Fant ingen data om deg"), ephemeral=False embed=embed_templates.error_warning(self.MSG_NO_DATA), ephemeral=False
) )
freq_list = {f"{word}": freq for word, freq in result} freq_list = {f"{word}": freq for word, freq in result}
embed = discord.Embed(description="Her er dataen jeg har lagret om deg") # Create Json file
buffer = StringIO() buffer = StringIO()
buffer.write(json.dumps(freq_list, indent=4)) buffer.write(json.dumps(freq_list, indent=4))
buffer.seek(0) buffer.seek(0)
file = discord.File(buffer, filename=f"uiog_word_freqs_{interaction.user.id}.json") file = discord.File(buffer, filename=f"uiog_word_freqs_{interaction.user.id}.json")
embed = discord.Embed(description="Her er dataen jeg har lagret om deg")
await interaction.response.send_message(embed=embed, file=file, ephemeral=True) await interaction.response.send_message(embed=embed, file=file, ephemeral=True)
@app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True) @app_commands.checks.bot_has_permissions(embed_links=True, attach_files=True)
@@ -352,7 +398,7 @@ class WordCloud(commands.Cog):
await interaction.response.defer() await interaction.response.defer()
# Clear cache first to ensure correct count # Clear cache first to ensure correct count
await self.batch_update_word_freqs() await self.insert_cache()
# Fetch word count from database # Fetch word count from database
self.cursor.execute( self.cursor.execute(
@@ -367,10 +413,12 @@ class WordCloud(commands.Cog):
if not results: if not results:
return await interaction.followup.send( return await interaction.followup.send(
embed=embed_templates.error_warning(interaction, text="Fant ingen data om deg"), ephemeral=False embed=embed_templates.error_warning(self.MSG_NO_DATA), ephemeral=False
) )
# Fetch tracking start time metadata # Fetch tracking start time metadata
# This doesn't work most of the time so consider using the API call instead of cache call
# Or consider removing this feature entirely
self.cursor.execute( self.cursor.execute(
""" """
SELECT tracked_since_message_channel_id, tracked_since_message_id SELECT tracked_since_message_channel_id, tracked_since_message_id
@@ -400,13 +448,11 @@ class WordCloud(commands.Cog):
word_cloud_file = discord.File(word_cloud, filename=f"wordcloud_{interaction.user.id}.png") word_cloud_file = discord.File(word_cloud, filename=f"wordcloud_{interaction.user.id}.png")
embed = discord.Embed(title="☁️ Her er ordskyen din! ☁️") embed = discord.Embed(title="☁️ Her er ordskyen din! ☁️")
embed.description = "Basert på de 4000 mest frekvente ordene dine siden "
if origin_found: if origin_found:
embed.description = ( embed.description += f"{origin_msg_timestamp}\n[Se melding]({origin_msg.jump_url})"
f"Basert på de 4000 mest frekvente ordene dine siden {origin_msg_timestamp}\n"
+ f"[Se melding]({origin_msg.jump_url})"
)
else: else:
embed.description = "Basert på de 4000 mest frekvente ordene dine siden `ukjent dato`" embed.description += "`ukjent dato`"
embed.set_image(url=f"attachment://wordcloud_{interaction.user.id}.png") embed.set_image(url=f"attachment://wordcloud_{interaction.user.id}.png")
await interaction.followup.send(embed=embed, file=word_cloud_file) await interaction.followup.send(embed=embed, file=word_cloud_file)
@@ -428,9 +474,10 @@ class WordCloud(commands.Cog):
await interaction.response.defer() await interaction.response.defer()
# Get member role for future channel permission checking # Get member role for future channel permission checking
member_role = interaction.guild.get_role(779849617651138601) # This shouldn't be hard coded but eh
board_member_role = interaction.guild.get_role(779849617651138601)
# Fetch last 1000 messages # This is horrible but there's a lot of edge cases to handle so spare me pls
all_messages = "" all_messages = ""
for channel in interaction.guild.text_channels: for channel in interaction.guild.text_channels:
if not channel.permissions_for(interaction.user).send_messages: if not channel.permissions_for(interaction.user).send_messages:
@@ -438,36 +485,27 @@ class WordCloud(commands.Cog):
# Avoid reading secret board member chats # Avoid reading secret board member chats
if ( if (
not channel.permissions_for(member_role).send_messages board_member_role # None if invoked outside of UiO Gaming's server. Avoiding crash due to AND check
and not channel.permissions_for(board_member_role).send_messages
and not channel.permissions_for(interaction.guild.default_role).send_messages and not channel.permissions_for(interaction.guild.default_role).send_messages
): ):
continue continue
try: try:
async for message in channel.history( # Limit search to half of requested messages to avoid rate limits
limit=int(antall / 2) # We assume we can still fetch enough tokens to reach the requested amount
): # Limit search in each channel to half of requested async for message in channel.history(limit=int(antall / 2)):
if message.author != interaction.user: if not self.can_count_message(message):
continue continue
# Filter bot command messages filtered_msg = " ".join(self.tokenize(message.clean_content))
if not message.clean_content[:2].isalpha():
continue
# Filter URLs
filtered_msg = re.sub(
r"https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)", # noqa: E501
"",
message.clean_content,
)
all_messages += f"{filtered_msg} " all_messages += f"{filtered_msg} "
except discord.errors.Forbidden: except discord.errors.Forbidden:
continue continue
if not all_messages: if not all_messages:
return await interaction.followup.send( return await interaction.followup.send(
embed=embed_templates.error_warning(interaction, text="Fant ingen data om deg"), ephemeral=False embed=embed_templates.error_warning(self.MSG_NO_DATA), ephemeral=False
) )
# Generate word cloud # Generate word cloud
@@ -495,5 +533,4 @@ async def setup(bot: commands.Bot):
bot (commands.Bot): Bot instance bot (commands.Bot): Bot instance
""" """
bot.add_listener(WordCloud(bot).word_freq_listener, "on_message")
await bot.add_cog(WordCloud(bot)) await bot.add_cog(WordCloud(bot))
+38 -48
View File
@@ -41,77 +41,67 @@ class Bot(commands.Bot):
self.logger = BotLogger().logger # Initialize logger self.logger = BotLogger().logger # Initialize logger
self.guild_id = config.get("dev_guild", UIO_GAMING_GUILD_ID) self.cog_files = set(listdir("./src/cogs"))
# Connect to database # Check for missing credentials
# We assume the config file is valid and contains the needed keys, even if they are empty if self.check_credentials(config["database"], DATABASE_RELIANT_COGS):
db = config.get("database")
for db_credential in db.values():
if not db_credential:
self.db_connection = None
self.logger.warning(
f"No database credentials specified. Disabling db reliant cogs:\n{DATABASE_RELIANT_COGS}"
)
break
else:
self.db_connection = psycopg2.connect( self.db_connection = psycopg2.connect(
host=db["host"], host=config["database"]["host"],
dbname=db["dbname"], dbname=config["database"]["dbname"],
user=db["username"], user=config["database"]["username"],
password=db["password"], password=config["database"]["password"],
) )
self.db_connection.autocommit = True self.db_connection.autocommit = True # Scary
for sanity_credential in config.get("sanity").values(): if self.check_credentials(config["sanity"], SANITY_RELIANT_COGS):
if not sanity_credential: self.sanity = config["sanity"]
self.has_sanity_credentials = False
self.logger.warning(
f"No Sanity credentials specified. Disabling sanity reliant cogs\n{SANITY_RELIANT_COGS}"
)
break
else:
self.has_sanity_credentials = True
if not config.get("minecraft").get("rcon_password"): if self.check_credentials(config["minecraft"], MINECRAFT_RELIANT_COGS):
self.has_minecraft_credentials = False self.mc_rcon_password = config["minecraft"]["rcon_password"]
self.logger.warning(
f"No Minecraft credentials specified. Disabling minecraft reliatn cogs\n{MINECRAFT_RELIANT_COGS}"
)
else:
self.has_minecraft_credentials = True
# Fetch misc config values # Fetch misc config values
self.mc_rcon_password = config["minecraft"]["rcon_password"] self.UIO_GAMING_GUILD_ID = UIO_GAMING_GUILD_ID
self.sanity = config["sanity"] self.guild_id = config.get("dev_guild", self.UIO_GAMING_GUILD_ID)
self.config_mode = config.get("config_mode")
self.presence = config["bot"].get("presence", {}) self.presence = config["bot"].get("presence", {})
self.emoji = config.get("emoji", {}) self.emoji = config.get("emoji", {})
self.misc = config.get("misc", {}) self.misc = config.get("misc", {})
self.config_mode = config.get("config_mode")
async def setup_hook(self): async def setup_hook(self):
cog_files = listdir("./src/cogs")
if self.db_connection is None:
cog_files = set(cog_files) - DATABASE_RELIANT_COGS
if not self.has_sanity_credentials:
cog_files = set(cog_files) - SANITY_RELIANT_COGS
if not self.has_minecraft_credentials:
cog_files = set(cog_files) - MINECRAFT_RELIANT_COGS
# Load cogs # Load cogs
for file in cog_files: for file in self.cog_files:
if file.endswith(".py"): if file.endswith(".py"):
name = file[:-3] name = file[:-3]
await bot.load_extension(f"cogs.{name}") await bot.load_extension(f"cogs.{name}")
# Sync slash commands to Discord # Sync slash commands
if self.config_mode == "prod": if self.config_mode == "prod":
await self.tree.sync() await self.tree.sync()
else: else:
self.tree.copy_global_to(guild=discord.Object(id=self.guild_id)) self.tree.copy_global_to(guild=discord.Object(id=self.guild_id))
await self.tree.sync(guild=discord.Object(id=self.guild_id)) await self.tree.sync(guild=discord.Object(id=self.guild_id))
def check_credentials(self, credentials: dict, dependant_cogs: set[str]) -> bool:
"""
Check if the credentials are valid. Removes cogs from the class' `cog_files` attribute if not
Parameters
-----------
credentials (dict): The credentials to check
dependant_cogs (set): The cogs that depend on the credentials
Returns
----------
bool: Whether the credentials are valid. If not, the dependant cogs are disabled.
"""
for credential in credentials.values():
if not credential:
self.logger.warning(f"Missing credentials. Disabling reliant cogs:\n{dependant_cogs}")
self.cog_files -= dependant_cogs
return False
return True
# Create bot instance # Create bot instance
bot = Bot() bot = Bot()