diff --git a/src/commands/activity/Activity.py b/src/commands/activity/Activity.py index d401f8a..4625e8d 100644 --- a/src/commands/activity/Activity.py +++ b/src/commands/activity/Activity.py @@ -1,9 +1,8 @@ from nextcord.ext import commands, tasks -from nextcord import \ - Status, \ - ActivityType import nextcord import random +import time +import os from utils.terminal import getlogger @@ -57,8 +56,22 @@ async def update_activity(self): try: activity = nextcord.Activity( type=ActivityType.playing, + type=nextcord.ActivityType.playing, name=(name:=random.choice([n for n in self.commands if n != self.command] if self.command else self.commands)), - state=self.names[name] + state=self.names[name], + details="testing", + url="https://github.com/GitGinocchio/GGsBot", + assets={ + "large_image": "https://github.com/GitGinocchio/GGsBot/blob/main/docs/media/banner.png?raw=true", + "large_text": "GGsBot", + "small_image": "https://github.com/GitGinocchio/GGsBot/blob/main/docs/media/circular_icon.png?raw=true", + "small_text": "GGsBot" + }, + buttons=[ + { "label" : "GitHub Repository", "url" : "https://github.com/GitGinocchio/GGsBot" }, + { "label" : "Developer", "url" : "https://github.com/GitGinocchio" } + ], + timestamps = { "start": int(time.time())} ) self.command = name @@ -66,7 +79,7 @@ async def update_activity(self): #logger.info(f"Changing bot activity to: {name}") await self.bot.change_presence(activity=activity) except Exception as e: - logger.error(e) + logger.exception(e) def setup(bot: commands.Bot) -> None: diff --git a/src/commands/games/CheapGames.py b/src/commands/games/CheapGames.py index 648b5a8..311c827 100644 --- a/src/commands/games/CheapGames.py +++ b/src/commands/games/CheapGames.py @@ -5,34 +5,6 @@ # allora non e' necessario inviare il messaggio """ -updated : [ - { - "name" : "", - "channels" : [], - "api" : "DEALS" - "stores" : "Steam" | etc. - "lowerPrice" : 0 | None # ritorna tutti i deal con prezzo superiore a lowerPrice - "upperPrice" : 0 | None # ritorna tutti i deal con prezzo inferiore ad upperPrice - "steamAppID" : 0 | None # guarda per determinati giochi di steam in base al loro ID - "saved" : { - gameID : publish_time - }, - "on" : "hour" - }, - { - "name" : "", - "channels" : [], - "api" : "GIVEAWAYS" - "type" : "game" | "loot" | "beta" | "all" - "stores" : "Steam" | etc. - "saved" : { - gameID : publish_time - }, - "on" : "hour" - }, - -] - updates : { name : { "channels" : [], @@ -350,7 +322,7 @@ async def update_giveaways_and_deals(self): async def handle_server_updates(self, configuration : tuple[int, str, bool, dict[str, dict[str, dict]]], skip_time_check : bool = False) -> dict: _, _, _, config = configuration for update_name, update_config in config['updates'].items(): - if time.localtime().tm_hour != int(update_config["on"]) and not skip_time_check: # IMPORTANT: I need to check if localtime is in UTC. + if datetime.datetime.now(datetime.UTC).hour != int(update_config["on"]) and not skip_time_check: continue if Api(update_config["api"]) == Api.GIVEAWAYS: @@ -384,11 +356,11 @@ async def send_giveaway_update(self, update_config : dict): games : list[dict] = await asyncget(url) for game in games: - if (game_id:=str(game["id"])) in saved_giveaways and game["published_date"] == saved_giveaways[game_id]: + if str(game["id"]) in saved_giveaways and game["published_date"] == saved_giveaways[str(game["id"])]: # Here we are checking if this giveaway is already registered continue - saved_giveaways[game_id] = game["published_date"] + saved_giveaways[str(game["id"])] = game["published_date"] for channel in giveaway_channels: if (channel:=self.bot.get_channel(channel)) is None: diff --git a/src/commands/general/RandomCats.py b/src/commands/general/RandomCats.py new file mode 100644 index 0000000..1aea3d6 --- /dev/null +++ b/src/commands/general/RandomCats.py @@ -0,0 +1,183 @@ +from nextcord import Embed,Color,Permissions +from nextcord.ext import commands + +from nextcord.ext.commands import Cog +from nextcord import \ + HTTPException, \ + Forbidden, \ + ButtonStyle, \ + SelectOption, \ + ChannelType, \ + SlashOption, \ + Interaction, \ + Colour, \ + Guild, \ + Embed, \ + File, \ + slash_command +from nextcord.ui import View, Button +from enum import StrEnum +from io import BytesIO +import re + +from utils.commons import safe_asyncget, asyncget + +class Font(StrEnum): + AndaleMono = "Andale Mono" + Impact = "Impact" + Arial = "Arial" + ArialBlack = "Arial Black" + ComicSansMS = "Comic Sans MS" + CourierNew = "Courier New" + Georgia = "Georgia" + TimesNewRoman = "Times New Roman" + Verdana = "Verdana" + Webdings = "Webdings" + +class ImageType(StrEnum): + SQUARE = "square" + MEDIUM = "medium" + SMALL = "small" + XSMALL = "xsmall" + +class Filter(StrEnum): + MONO = "mono" + NEGATE = "negate" + CUSTOM = "custom" + +class Fit(StrEnum): + COVER = "cover" + CONTAIN = "contain" + FILL = "fill" + INSIDE = "inside" + OUTSIDE = "outside" + +class Position(StrEnum): + TOP = "top" + LEFT = "left" + RIGHT = "right" + CENTER = "center" + BOTTOM = "bottom" + + LEFT_TOP = "left top" + LEFT_BOTTOM = "left bottom" + + RIGHT_TOP = "right top" + RIGHT_BOTTOM = "right bottom" + + +class RandomCats(commands.Cog): + def __init__(self, bot : commands.Bot): + commands.Cog.__init__(self) + self.baseurl = "https://cataas.com" + self.bot = bot + self.tags = None + self.fetched_tags = False + + @Cog.listener() + async def on_ready(self): + if not self.fetched_tags: + self.tags : list = await asyncget(f'{self.baseurl}/api/tags') + + @slash_command(name="cats", description="Set of commands to get cat images") + async def cats(self, interaction : Interaction): pass + + @cats.subcommand(name="tags",description="Get all available tags") + async def get_tags(self, interaction : Interaction): + try: + await interaction.response.defer(ephemeral=True) + assert self.tags is not None, "Tags are not loaded at the moment" + + + embed = Embed( + title="Available Tags", + description="You can see the available tags below", + color=Color.green() + ) + + view = View(timeout=3) + link = Button(style=ButtonStyle.url, label="All available tags", url=f"{self.baseurl}/api/tags") + view.add_item(link) + + except AssertionError as e: + await interaction.followup.send(e, ephemeral=True, delete_after=5) + else: + await interaction.followup.send(embed=embed, view=view, ephemeral=True) + + @cats.subcommand(name="randomcat",description="Get a random cat image") + @commands.cooldown(1, 60, commands.BucketType.user) + async def randomcat(self, + interaction : Interaction, + tags : str = SlashOption(description="send a random image of a cat based on tags e.g. gif,cute", default="", required=False), + text : str = SlashOption(description="Text to be displayed on the image", default=" ", required=False), + font : str = SlashOption(description="Font of the text (default: Impact)", default=Font.Impact.value, choices=Font, required=False), + fontsize : int = SlashOption(description="Size of the font (min: 10, max: 100, default: 30)", default=30, required=False, max_value=100, min_value=10), + fontcolor : str = SlashOption(description="Color of the font e.g. #fffffff or white (default: white)", default="white", required=False), + fontbackground : str = SlashOption(description="Background color of the font e.g. #fffffff or white", default=None, required=False), + type : str = SlashOption(description="Type of the image (default: square)", choices=ImageType, default=ImageType.SQUARE, required=False), + filter : str | None = SlashOption(description="Filter of the image", choices=Filter, default=None, required=False), + fit : str | None = SlashOption(description="Fit of the image", choices=Fit, default=None, required=False), + position : str = SlashOption(description="Position of the text (default: bottom)", default=Position.BOTTOM, choices=Position, required=False), + width : int | None = SlashOption(description="Width of the image (min: 100, max: 1920)", default=None, required=False, min_value=100, max_value=1920), + height : int | None = SlashOption(description="Height of the image (min: 100, max: 1080)", default=None, required=False, min_value=100, max_value=1080), + blur : int | None = SlashOption(description="Blur of the image (min: 0, max: 10, default: None)", default=None, required=False, min_value=0, max_value=10), + red : int | None = SlashOption(description="Red value of the image (min: 0, max: 255, default: 255)", default=None, required=False, min_value=0, max_value=255), + green : int | None = SlashOption(description="Green value of the image (min: 0, max: 255, default: 255)", default=None, required=False, min_value=0, max_value=255), + blue : int | None = SlashOption(description="Blue value of the image (min: 0, max: 255, default: 255)", default=None, required=False, min_value=0, max_value=255), + brightness : float | None = SlashOption(description="Brightness multiplier of the image (min: -100.0, max: 100.0, default: None)", default=None, required=False, min_value=-100, max_value=100), + saturation : float | None = SlashOption(description="Saturation multiplier of the image (min: -100.0, max: 100.0, default: None)", default=None, required=False, min_value=-100, max_value=100), + hue : int | None = SlashOption(description="Hue rotation of the image (min: -360, max: 360, default: 0)", default=None, required=False, min_value=-360, max_value=360), + lightness : int | None = SlashOption(description="Lightness addend of the image (min: -100, max: 100, default: 0)", default=None, required=False, min_value=-100, max_value=100), + spoiler : bool = SlashOption(description="Send the image as a spoiler", default=False, required=False), + ephemeral : bool = SlashOption(description="Send the image as ephemeral", default=False, required=False) + ): + try: + await interaction.response.defer(ephemeral=ephemeral) + + tags_pattern = r"^\S+(?:,\S+)*$" # comma separated list of strings regex + assert re.match(tags_pattern, tags), "Tags must be a comma separated list of strings" + for tag in tags.split(','): assert tag in self.tags, f"Tag \'{tag}\' is not an available tag" + + params = [ + f'font={font}', + f'fontSize={fontsize}', + f'fontColor={fontcolor}', + f'type={type}', + f'position={position}', + ] + + if filter: params.append(f'filter={filter}') + if fit: params.append(f'fit={fit}') + if width: params.append(f'width={width}') + if height: params.append(f'height={height}') + if brightness: params.append(f'brightness={brightness}') + if saturation: params.append(f'saturation={saturation}') + if red: params.append(f'red={red}') + if green: params.append(f'green={green}') + if blue: params.append(f'blue={blue}') + if hue: params.append(f'hue={hue}') + if lightness: params.append(f'lightness={lightness}') + if blur: params.append(f'blur={blur}') + if fontbackground: params.append(f'fontBackground={fontbackground}') + + url = f'{self.baseurl}/cat/{tags}/says/{text.replace(' ','%20')}?{'&'.join(params)}' + + print(url) + + content_type, content, status, reason = await safe_asyncget(url) + + assert status != 404, f"Cat not found with tags {tags} " + assert status == 200 and content_type.startswith("image/"), f"An unexpected error occurred: {reason}" + + ext = content_type.split('/')[1] + filelike = BytesIO(content) + filelike.seek(0) + + file = File(filelike, filename=f"cat.{ext}", description=f"Cat says {text}", spoiler=spoiler) + except AssertionError as e: + await interaction.followup.send(e, ephemeral=True, delete_after=5) + else: + await interaction.followup.send(file=file, ephemeral=ephemeral) + +def setup(bot : commands.Bot): + bot.add_cog(RandomCats(bot)) \ No newline at end of file diff --git a/src/main.py b/src/main.py index 45e6739..0c8495b 100644 --- a/src/main.py +++ b/src/main.py @@ -29,7 +29,7 @@ def load_commands(): categories = [c for c in os.listdir('./src/commands') if c not in config['ignore_categories']] - logger.info('Loading commands...') + logger.info('Loading extensions...') for category in categories: #logger.info(f'Looking in commands.{category}...') for filename in os.listdir(f'./src/commands/{category}'): @@ -45,7 +45,7 @@ def load_commands(): except commands.ExtensionFailed as e: logger.warning(e) else: - logger.info(f'Imported command {F.LIGHTMAGENTA_EX}{category}.{filename[:-3]}{F.RESET}') + logger.info(f'Imported extension {F.LIGHTMAGENTA_EX}{category}.{filename[:-3]}{F.RESET}') elif filename in config['ignore_files']: continue diff --git a/src/utils/terminal.py b/src/utils/terminal.py index 9321802..c507692 100644 --- a/src/utils/terminal.py +++ b/src/utils/terminal.py @@ -1,6 +1,7 @@ from utils.config import config from colorama import Fore as F from datetime import datetime +import subprocess import inspect import logging import sys @@ -9,11 +10,10 @@ def clear(): """call the command for clearing the terminal depending on your system""" - os.system('cls' if os.name == 'nt' else 'clear') + subprocess.run("cls" if os.name == "nt" else "clear", shell=True) def erase(): """Erase last terminal line (this should work on all systems)""" - print('') sys.stdout.write('\033[F') sys.stdout.write('\033[K') @@ -28,7 +28,7 @@ def erase(): class CustomColorsFormatter(logging.Formatter): def format(self, record : logging.LogRecord): - color = levels.get(record.levelname, F.WHITE) + color = levels.get(record.levelname, (logging.INFO, F.WHITE)) record.name = f"{F.LIGHTMAGENTA_EX}[{record.name}]{F.RESET}" record.msg = f": {color[1]}{record.msg}{F.RESET}" record.levelname = f"{color[1]}[{record.levelname}]{F.RESET}" @@ -53,7 +53,12 @@ def format(self, record : logging.LogRecord): def getlogger(name : str = None) -> logging.Logger: if name is None: - name = re.match(r".*[\\/](.+?)(\.[^.]*$|$)", inspect.stack()[1].filename).group(1) + match = re.match(r".*[\\/](.+?)(\.[^.]*$|$)", inspect.stack()[1].filename) + + if match: + name = match.group(1) + else: + name = "unknown" logger = logging.getLogger(name)