diff --git a/bot.py b/bot.py new file mode 100644 index 0000000..e69de29 diff --git a/cogs/fun.py b/cogs/fun.py new file mode 100644 index 0000000..555b286 --- /dev/null +++ b/cogs/fun.py @@ -0,0 +1,192 @@ +import random + +import aiohttp +import discord +from discord.ext import commands +from discord.ext.commands import Context + +from helpers import checks + + +class Choice(discord.ui.View): + def __init__(self): + super().__init__() + self.value = None + + @discord.ui.button(label="Heads", style=discord.ButtonStyle.blurple) + async def confirm(self, button: discord.ui.Button, interaction: discord.Interaction): + self.value = "heads" + self.stop() + + @discord.ui.button(label="Tails", style=discord.ButtonStyle.blurple) + async def cancel(self, button: discord.ui.Button, interaction: discord.Interaction): + self.value = "tails" + self.stop() + + +class RockPaperScissors(discord.ui.Select): + def __init__(self): + options = [ + discord.SelectOption( + label="Scissors", description="You choose scissors.", emoji="โœ‚" + ), + discord.SelectOption( + label="Rock", description="You choose rock.", emoji="๐Ÿชจ" + ), + discord.SelectOption( + label="paper", description="You choose paper.", emoji="๐Ÿงป" + ), + ] + super().__init__( + placeholder="Choose...", + min_values=1, + max_values=1, + options=options, + ) + + async def callback(self, interaction: discord.Interaction): + choices = { + "rock": 0, + "paper": 1, + "scissors": 2, + } + user_choice = self.values[0].lower() + user_choice_index = choices[user_choice] + + bot_choice = random.choice(list(choices.keys())) + bot_choice_index = choices[bot_choice] + + result_embed = discord.Embed(color=0x9C84EF) + result_embed.set_author( + name=interaction.user.name, + icon_url=interaction.user.avatar.url + ) + + if user_choice_index == bot_choice_index: + result_embed.description = f"**That's a draw!**\nYou've chosen {user_choice} and I've chosen {bot_choice}." + result_embed.colour = 0xF59E42 + elif user_choice_index == 0 and bot_choice_index == 2: + result_embed.description = f"**You won!**\nYou've chosen {user_choice} and I've chosen {bot_choice}." + result_embed.colour = 0x9C84EF + elif user_choice_index == 1 and bot_choice_index == 0: + result_embed.description = f"**You won!**\nYou've chosen {user_choice} and I've chosen {bot_choice}." + result_embed.colour = 0x9C84EF + elif user_choice_index == 2 and bot_choice_index == 1: + result_embed.description = f"**You won!**\nYou've chosen {user_choice} and I've chosen {bot_choice}." + result_embed.colour = 0x9C84EF + else: + result_embed.description = f"**I won!**\nYou've chosen {user_choice} and I've chosen {bot_choice}." + result_embed.colour = 0xE02B2B + await interaction.response.edit_message(embed=result_embed, content=None, view=None) + + +class RockPaperScissorsView(discord.ui.View): + def __init__(self): + super().__init__() + self.add_item(RockPaperScissors()) + + +class Fun(commands.Cog, name="fun"): + def __init__(self, bot): + self.bot = bot + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="randomfact", + description="Get a random fact." + ) + @checks.not_blacklisted() + async def randomfact(self, context: Context) -> None: + + # This will prevent your bot from stopping everything when doing a web request - see: https://discordpy.readthedocs.io/en/stable/faq.html#how-do-i-make-a-web-request + async with aiohttp.ClientSession() as session: + async with session.get("https://uselessfacts.jsph.pl/random.json?language=en") as request: + if request.status == 200: + data = await request.json() + embed = discord.Embed( + description=data["text"], + color=0xD75BF4 + ) + else: + embed = discord.Embed( + title="Error!", + description="There is something wrong with the API, please try again later", + color=0xE02B2B + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="randomquote", + description="Get a random quote." + ) + @checks.not_blacklisted() + async def quote(self, context) -> None: + + async with aiohttp.ClientSession() as cs: + async with cs.get("https://zenquotes.io/api/random") as r: + res = await r.json() # returns dict + quote = res[0]['q'] + " -" + res[0]['a'] + await context.send(quote) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="randomtopic", + description="Get a random topic." + ) + @checks.not_blacklisted() + async def topic(self, context) -> None: + """Provides a question for users to talk about""" + topics = ["What is your favourite book?", "What is your favourite game?", + "What is your favourite song with a positive message?", "What is your favourite place to visit?", + "Did you apply what you learned in school?", "What is your favourite programming language?", "What is your favourite website?"] + number = random.randint(0, len(topics)) + await context.send(topics[number]) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="coinflip", + description="Make a coin flip, but give your bet before." + ) + @checks.not_blacklisted() + async def coinflip(self, context: Context) -> None: + + buttons = Choice() + embed = discord.Embed( + description="What is your bet?", + color=0x9C84EF + ) + message = await context.send(embed=embed, view=buttons) + await buttons.wait() # We wait for the user to click a button. + result = random.choice(["heads", "tails"]) + if buttons.value == result: + embed = discord.Embed( + description=f"Correct! You guessed `{buttons.value}` and I flipped the coin to `{result}`.", + color=0x9C84EF + ) + else: + embed = discord.Embed( + description=f"Woops! You guessed `{buttons.value}` and I flipped the coin to `{result}`, better luck next time!", + color=0xE02B2B + ) + await message.edit(embed=embed, view=None, content=None) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="rps", + description="Play the rock paper scissors game against the bot." + ) + @checks.not_blacklisted() + async def rock_paper_scissors(self, context: Context) -> None: + + view = RockPaperScissorsView() + await context.send("Please make your choice", view=view) + + +async def setup(bot): + await bot.add_cog(Fun(bot)) \ No newline at end of file diff --git a/cogs/general.py b/cogs/general.py new file mode 100644 index 0000000..395de36 --- /dev/null +++ b/cogs/general.py @@ -0,0 +1,457 @@ +import platform +import os +import platform +import sys +import psutil +import math +import copy + +import random +from random import randint + +import datetime +import time +from time import time +from datetime import datetime +from datetime import datetime as dt + +#from discord.utils import get +from discord.utils import * + +import aiohttp +import discord +from discord import app_commands +from discord import Embed, Color +from discord.ext import commands +from discord.ext.commands import Context + +from http.client import responses + +from paginator import Paginator + +from typing import Optional +from constants import ( + LANGUAGES, + NEWLINES_LIMIT, + CHARACTERS_LIMIT, + Emoji, + JUDGE0_ICON, + START_TIME, + PREFIX, +) + + + + + + + + + + + + +from helpers import checks + + + + + + + + + + + +class General(commands.Cog, name="general"): + def __init__(self, bot): + self.bot = bot + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="help", + description="List all commands the bot has loaded." + ) + @checks.not_blacklisted() + async def help(self, context: Context) -> None: + prefix = self.bot.config["prefix"] + embed = discord.Embed( + title="Help", description="List of available commands:", color=0x9C84EF) + for i in self.bot.cogs: + cog = self.bot.get_cog(i.lower()) + commands = cog.get_commands() + data = [] + for command in commands: + description = command.description.partition('\n')[0] + data.append(f"{prefix}{command.name} - {description}") + help_text = "\n".join(data) + embed.add_field(name=i.capitalize(), + value=f'```{help_text}```', inline=False) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + @commands.hybrid_command( + name="sinfo", + description="List bot server status info." + ) + @checks.not_blacklisted() + async def sinfo(self, ctx): + #Sends useful information about the bot and links. + uptime = int((dt.utcnow() - START_TIME).total_seconds()) + d, h = divmod(uptime, 86400) + h, m = divmod(h, 3600) + m, s = divmod(m, 60) + mem = psutil.virtual_memory() + pid = os.getpid() + memory_use = psutil.Process(pid).memory_info()[0] + data = [ + ("Process memory", f"{memory_use / math.pow(1024, 2):.2f}MB"), + ("CPU Usage", f"{psutil.cpu_percent()}%"), + ("RAM Usage", f"{mem.percent}%"), + ] + embed = Embed( + title="BOT System", + url="https://discord.gg/ScektXnyBe", + timestamp=dt.utcnow(), + description="Discord bot for single custom servers", + ) + + embed.set_author(name=f"{ctx.author} request", icon_url=ctx.author.avatar) + embed.add_field( + name=f"YADMB#9063 - {self.bot.config['application_id']}", + value=( + f"Uptime: {d}d {h}h {m}m {s}s\n" + f"Servers connected: {len(self.bot.guilds)}\n" + f"Unique users: {len(self.bot.users)}" + f"Running on: {platform.system()} {platform.release()} ({os.name})" + ), + inline=False + ) + embed.add_field( + name=":computer: System status:", + value="\n".join(f"**{x[0]}** {x[1]}" for x in data), + inline=False + ) + embed.add_field( + name="Links", + value=( + f":link: Invite me by clicking [here](https://discordapp.com/oauth2/authorize?&client_id={self.bot.config['application_id']}&scope=bot+applications.commands&permissions={self.bot.config['permissions']}).\n" + f":mortar_board: [GitHub](https://github.com/Die-Antwoord)\n" + f":information_source: [Support server](https://discord.gg/ScektXnyBe)\n" + #f"<:paypal:707665144276320277> [Donation on PayPal](https://paypal.me/skilldeliver)\n" + #f"<:patreon:707663083866161232> [Support me on Patreon](https://www.patreon.com/vmihov)" + ), + inline=False + ) + embed.set_thumbnail(url=JUDGE0_ICON) + await ctx.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="botinfo", + description="Get some useful (or not) information about the bot.", + ) + @checks.not_blacklisted() + async def botinfo(self, context: Context) -> None: + + mem = psutil.virtual_memory() + pid = os.getpid() + memory_use = psutil.Process(pid).memory_info()[0] + data = [ + ("Process memory", f"{memory_use / math.pow(1024, 2):.2f}MB"), + ("CPU Usage", f"{psutil.cpu_percent()}%"), + ("RAM Usage", f"{mem.percent}%"), + ] + embed = discord.Embed( + description=":id: GitHUB\n[Die-Antwoord](https://github.com/Die-Antwoord) ", + color=0x9C84EF + ) + embed.set_author( + name="Bot Information" + ) + embed.add_field( + name=":mortar_board: Bot Owner:", + value="Die Antwoord#1337", + inline=False + ) + embed.add_field( + name=":keyboard: Python Version:", + value=f"Python {platform.python_version()}", + inline=True + ) + embed.add_field( + name=":pager: OS Version:", + value=(f"Running on: {platform.system()} {platform.release()} ({os.name})"), + inline=True + ) + embed.add_field( + name=":computer: System status:", + value="\n".join(f"**{x[0]}** {x[1]}" for x in data), + inline=False + ) + embed.add_field( + name=":information_source: Prefix:", + value=f"`/` (Slash Commands)\nor\n`{self.bot.config['prefix']}` for normal commands", + inline=False + ) + embed.set_footer( + text=f"Requested by {context.author}\nRequested at {context.guild.created_at}" + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + # @commands.hybrid_command( + # name="system", + # description="Get some useful (or not) information about the system.", + # ) + # #@commands.command(aliases=["status"]) + # async def system(self, ctx: commands.Context): + # """Get status of the host system""" + # process_uptime = time.time() - self.bot.start_time + # system_uptime = time.time() - psutil.boot_time() + # mem = psutil.virtual_memory() + # pid = os.getpid() + # memory_use = psutil.Process(pid).memory_info()[0] + + # data = [ + # ("Bot booted up in", util.stringfromtime(self.bot.boot_up_time)), + # ("Process uptime", util.stringfromtime(process_uptime, 2)), + # ("Process memory", f"{memory_use / math.pow(1024, 2):.2f}MB"), + # ("System uptime", util.stringfromtime(system_uptime, 2)), + # ("CPU Usage", f"{psutil.cpu_percent()}%"), + # ("RAM Usage", f"{mem.percent}%"), + # ] + + # content = discord.Embed( + # title=":computer: System status", + # colour=int("5dadec", 16), + # description="\n".join(f"**{x[0]}** {x[1]}" for x in data), + # ) + # await ctx.send(embed=content) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="serverinfo", + description="Get some useful (or not) information about the server.", + ) + @checks.not_blacklisted() + async def serverinfo(self, context: Context) -> None: + roles = [role.mention for role in context.guild.roles] + if len(roles) > 30: + roles = roles[:30] + roles.append (f"Displaying[30/{len(roles)} Roles") + roles = ", ".join(roles) + + embed = discord.Embed( + title="**Server Name:**", + description=f"{context.guild}", + color=0x9C84EF + ) + if context.guild.icon is not None: + embed.set_thumbnail( + url=context.guild.icon.url + ) + embed.add_field( + name="Server ID", + value=context.guild.id, + inline=True + ) + embed.add_field( + name="Member Count", + value=context.guild.member_count, + inline=True + ) + embed.add_field( + name="Text/Voice Channels", + value=f"{len(context.guild.channels)}", + inline=True + ) + embed.add_field( + name=f"Roles ({len(context.guild.roles)})", + value=roles + ) + embed.set_footer( + text=f"Created at: {context.guild.created_at}" + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="ping", + description="Check if the bot is alive.", + ) + @checks.not_blacklisted() + async def ping(self, context: Context) -> None: + embed = discord.Embed( + title="The bot latency is ", + description=f"**{round(self.bot.latency * 1000)}ms.**", + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="invite", + description="Get the invite link of the bot to be able to invite it.", + ) + @checks.not_blacklisted() + async def invite(self, context: Context) -> None: + embed = discord.Embed( + title="๐Ÿ”‘ Permission Administrator", + description=f"Invite me by clicking [here](https://discordapp.com/oauth2/authorize?&client_id={self.bot.config['application_id']}&scope=bot+applications.commands&permissions={self.bot.config['permissions']}).", + color=0xD75BF4 + ) + try: + # To know what permissions to give to your bot, please see here: https://discordapi.com/permissions.html and remember to not give Administrator permissions. + await context.author.send(embed=embed) + await context.send("I sent you a private message!") + except discord.Forbidden: + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="server", + description="Get the invite link of the discord server of the bot for some support.", + ) + @checks.not_blacklisted() + async def server(self, context: Context) -> None: + embed = discord.Embed( + description=f"Join the support server for the bot by clicking [here](https://discord.gg/ScektXnyBe).", + color=0xD75BF4 + ) + try: + await context.author.send(embed=embed) + await context.send("I sent you a private message!") + except discord.Forbidden: + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="8ball", + description="Ask any question to the bot.", + ) + @checks.not_blacklisted() + @app_commands.describe(question="The question you want to ask.") + async def eight_ball(self, context: Context, *, question: str) -> None: + + answers = ["It is certain.", "It is decidedly so.", "You may rely on it.", "Without a doubt.", + "Yes - definitely.", "As I see, yes.", "Most likely.", "Outlook good.", "Yes.", + "Signs point to yes.", "Reply hazy, try again.", "Ask again later.", "Better not tell you now.", + "Cannot predict now.", "Concentrate and ask again later.", "Don't count on it.", "My reply is no.", + "My sources say no.", "Outlook not so good.", "Very doubtful."] + embed = discord.Embed( + title="**My Answer:**", + description=f"{random.choice(answers)}", + color=0x9C84EF + ) + embed.set_footer( + text=f"The question was: {question}" + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="bitcoin", + description="Get the current price of bitcoin.", + ) + @checks.not_blacklisted() + async def bitcoin(self, context: Context) -> None: + + # This will prevent your bot from stopping everything when doing a web request - see: https://discordpy.readthedocs.io/en/stable/faq.html#how-do-i-make-a-web-request + async with aiohttp.ClientSession() as session: + async with session.get("https://api.coindesk.com/v1/bpi/currentprice/BTC.json") as request: + if request.status == 200: + data = await request.json( + content_type="application/javascript") # For some reason the returned content is of type JavaScript + embed = discord.Embed( + title="Bitcoin price", + description=f"The current price is {data['bpi']['USD']['rate']} :dollar:", + color=0x9C84EF + ) + else: + embed = discord.Embed( + title="Error!", + description="There is something wrong with the API, please try again later", + color=0xE02B2B + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="rolecount", + description="Get the current rolecount of the server.", + ) + @checks.not_blacklisted() + @checks.is_owner() + async def rolecount(self, context) -> None: + + data = format(len(context.guild.roles) - 1) + embed = discord.Embed( + title="Total Group Roles", + description=f"\n**`{data}`**", + color=0x9C84EF + ) + embed.set_footer( + text=f"Created at: {context.guild.created_at}" + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +async def setup(bot): + await bot.add_cog(General(bot)) diff --git a/cogs/infomantion.py b/cogs/infomantion.py new file mode 100644 index 0000000..187a3f9 --- /dev/null +++ b/cogs/infomantion.py @@ -0,0 +1,64 @@ +import discord +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from helpers import checks + + +# Here we name the cog and create a new class for the cog. +class Infomantion(commands.Cog, name="infomantion"): + def __init__(self, bot): + self.bot = bot + + # Here you can just add your own commands, you'll always need to provide "self" as first parameter. + + + @commands.hybrid_command( + name='links', + description="This is a testing command that does nothing.", + aliases=['link'] + ) + @checks.not_blacklisted() + async def links(self, context): + """Lists all my important links in an embed.""" + links = discord.Embed( + title="List of Links", + description="Here is a list of links. More will be added later. Enjoy!", + colour=0xFF0000 + ) + links.set_author( + name="Die-Antwoord" + ) + links.add_field( + name="Aero#5703 - 432129282710700033", + value="https://aero.bot/", + inline=False + ) + links.add_field( + name="Reputation#1740 - 550035183269838848", + value="https://discordrep.com/", + inline=False + ) + links.add_field( + name="Blacklister#3409 - 866364881917837312", + value="https://discord.gg/FZxgNrwBS6", + inline=False + ) + links.add_field( + name="DangerCord - 1007806961854726269", + value="โ–Œ<@1007806961854726269> | https://dangercord.com/", + inline=False + ) + links.add_field( + name="GitHub", + value="https://github.com/Die-Antwoord", + inline=False + ) + await context.send(embed=links) + pass + + +# And then we finally add the cog to the bot so that it can load, unload, reload and use it's content. +async def setup(bot): + await bot.add_cog(Infomantion(bot)) \ No newline at end of file diff --git a/cogs/lockchannel.py b/cogs/lockchannel.py new file mode 100644 index 0000000..b8e0009 --- /dev/null +++ b/cogs/lockchannel.py @@ -0,0 +1,49 @@ +import discord + +from discord.ext import commands +from discord.ext.commands import Context + +from helpers import checks + + +class Lockchannel(commands.Cog, name="lockchannel"): + def __init__(self, bot): + self.bot = bot + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="lockchannel", + description="Lock Server Channel.", + ) + @commands.has_permissions(manage_channels=True) + @checks.not_blacklisted() + @checks.is_owner() + async def lock(self, context, channel: discord.TextChannel = None, reason=None) -> None: + if reason == None: + reason = 'Channel Locked By Owner' + channel = context.channel or channel + await channel.set_permissions(context.guild.default_role, send_messages=False, add_reactions=False, create_public_threads=False) + await context.send(f"`{context.author}` - `{context.author.id}` has locked <#{channel.id}>\nReason: `{reason}`") + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="unlockchannel", + description="Unlock Server Channel.", + ) + @commands.has_permissions(manage_channels=True) + @checks.not_blacklisted() + @checks.is_owner() + async def unlock(self, context, channel: discord.TextChannel = None, reason=None) -> None: + if reason == None: + reason = 'Channel Locked By Owner' + channel = context.channel + await channel.set_permissions(context.guild.default_role, send_messages=None, add_reactions=None, create_public_threads=None) + await context.send(f"`{context.author}` - `{context.author.id}` has unlocked <#{channel.id}>\nReason: `{reason}`") + +#------------------------------------------------------------------------------- + + +async def setup(bot): + await bot.add_cog(Lockchannel(bot)) diff --git a/cogs/moderation.py b/cogs/moderation.py new file mode 100644 index 0000000..639a63a --- /dev/null +++ b/cogs/moderation.py @@ -0,0 +1,266 @@ +import discord +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from helpers import checks, db_manager + + +class Moderation(commands.Cog, name="moderation"): + def __init__(self, bot): + self.bot = bot + + @commands.hybrid_command( + name="kick", + description="Kick a user out of the server.", + ) + @commands.has_permissions(kick_members=True) + @commands.bot_has_permissions(kick_members=True) + @checks.not_blacklisted() + @app_commands.describe(user="The user that should be kicked.", reason="The reason why the user should be kicked.") + async def kick(self, context: Context, user: discord.User, *, reason: str = "Not specified") -> None: + member = context.guild.get_member(user.id) or await context.guild.fetch_member(user.id) + if member.guild_permissions.administrator: + embed = discord.Embed( + title="Error!", + description="User has Admin permissions.", + color=0xE02B2B + ) + await context.send(embed=embed) + else: + try: + embed = discord.Embed( + title="User Kicked!", + description=f"**{member}** was kicked by **{context.author}**!", + color=0x9C84EF + ) + embed.add_field( + name="Reason:", + value=reason + ) + await context.send(embed=embed) + try: + await member.send( + f"You were kicked by **{context.author}**!\nReason: {reason}" + ) + except: + # Couldn't send a message in the private messages of the user + pass + await member.kick(reason=reason) + except: + embed = discord.Embed( + title="Error!", + description="An error occurred while trying to kick the user. Make sure my role is above the role of the user you want to kick.", + color=0xE02B2B + ) + await context.send(embed=embed) + + @commands.hybrid_command( + name="nick", + description="Change the nickname of a user on a server.", + ) + @commands.has_permissions(manage_nicknames=True) + @commands.bot_has_permissions(manage_nicknames=True) + @checks.not_blacklisted() + @app_commands.describe(user="The user that should have a new nickname.", nickname="The new nickname that should be set.") + async def nick(self, context: Context, user: discord.User, *, nickname: str = None) -> None: + + member = context.guild.get_member(user.id) or await context.guild.fetch_member(user.id) + try: + await member.edit(nick=nickname) + embed = discord.Embed( + title="Changed Nickname!", + description=f"**{member}'s** new nickname is **{nickname}**!", + color=0x9C84EF + ) + await context.send(embed=embed) + except: + embed = discord.Embed( + title="Error!", + description="An error occurred while trying to change the nickname of the user. Make sure my role is above the role of the user you want to change the nickname.", + color=0xE02B2B + ) + await context.send(embed=embed) + + @commands.hybrid_command( + name="ban", + description="Bans a user from the server.", + ) + @commands.has_permissions(ban_members=True) + @commands.bot_has_permissions(ban_members=True) + @checks.not_blacklisted() + @app_commands.describe(user="The user that should be banned.", reason="The reason why the user should be banned.") + async def ban(self, context: Context, user: discord.User, *, reason: str = "Not specified") -> None: + + member = context.guild.get_member(user.id) or await context.guild.fetch_member(user.id) + try: + if member.guild_permissions.administrator: + embed = discord.Embed( + title="Error!", + description="User has Admin permissions.", + color=0xE02B2B + ) + await context.send(embed=embed) + else: + embed = discord.Embed( + title="User Banned!", + description=f"**{member}** was banned by **{context.author}**!", + color=0x9C84EF + ) + embed.add_field( + name="Reason:", + value=reason + ) + await context.send(embed=embed) + try: + await member.send(f"You were banned by **{context.author}**!\nReason: {reason}") + except: + # Couldn't send a message in the private messages of the user + pass + await member.ban(reason=reason) + except: + embed = discord.Embed( + title="Error!", + description="An error occurred while trying to ban the user. Make sure my role is above the role of the user you want to ban.", + color=0xE02B2B + ) + await context.send(embed=embed) + + @commands.hybrid_group( + name="warning", + description="Manage warnings of a user on a server.", + ) + @commands.has_permissions(manage_messages=True) + @checks.not_blacklisted() + async def warning(self, context: Context) -> None: + + if context.invoked_subcommand is None: + embed = discord.Embed( + title="Error!", + description="Please specify a subcommand.\n\n**Subcommands:**\n`add` - Add a warning to a user.\n`remove` - Remove a warning from a user.\n`list` - List all warnings of a user.", + color=0xE02B2B + ) + await context.send(embed=embed) + + @warning.command( + name="add", + description="Adds a warning to a user in the server.", + ) + @checks.not_blacklisted() + @commands.has_permissions(manage_messages=True) + @app_commands.describe(user="The user that should be warned.", reason="The reason why the user should be warned.") + async def warning_add(self, context: Context, user: discord.User, *, reason: str = "Not specified") -> None: + + member = context.guild.get_member(user.id) or await context.guild.fetch_member(user.id) + total = await db_manager.add_warn( + user.id, context.guild.id, context.author.id, reason) + embed = discord.Embed( + title="User Warned!", + description=f"**{member}** was warned by **{context.author}**!\nTotal warns for this user: {total}", + color=0x9C84EF + ) + embed.add_field( + name="Reason:", + value=reason + ) + await context.send(embed=embed) + try: + await member.send(f"You were warned by **{context.author}**!\nReason: {reason}") + except: + # Couldn't send a message in the private messages of the user + await context.send(f"{member.mention}, you were warned by **{context.author}**!\nReason: {reason}") + + @warning.command( + name="remove", + description="Removes a warning from a user in the server.", + ) + @checks.not_blacklisted() + @commands.has_permissions(manage_messages=True) + @app_commands.describe(user="The user that should get their warning removed.", warn_id="The ID of the warning that should be removed.") + async def warning_remove(self, context: Context, user: discord.User, warn_id: int) -> None: + + member = context.guild.get_member(user.id) or await context.guild.fetch_member(user.id) + total = await db_manager.remove_warn(warn_id, user.id, context.guild.id) + embed = discord.Embed( + title="User Warn Removed!", + description=f"I've removed the warning **#{warn_id}** from **{member}**!\nTotal warns for this user: {total}", + color=0x9C84EF + ) + await context.send(embed=embed) + + @warning.command( + name="list", + description="Shows the warnings of a user in the server.", + ) + @commands.has_guild_permissions(manage_messages=True) + @checks.not_blacklisted() + @app_commands.describe(user="The user you want to get the warnings of.") + async def warning_list(self, context: Context, user: discord.User): + + warnings_list = await db_manager.get_warnings(user.id, context.guild.id) + embed = discord.Embed( + title=f"Warnings of {user}", + color=0x9C84EF + ) + description = "" + if len(warnings_list) == 0: + description = "This user has no warnings." + else: + for warning in warnings_list: + description += f"โ€ข Warned by <@{warning[2]}>: **{warning[3]}** () - Warn ID #{warning[5]}\n" + embed.description = description + await context.send(embed=embed) + + @commands.hybrid_command( + name="purge", + description="Delete a number of messages.", + ) + @commands.has_guild_permissions(manage_messages=True) + @commands.bot_has_permissions(manage_messages=True) + @checks.not_blacklisted() + @app_commands.describe(amount="The amount of messages that should be deleted.") + async def purge(self, context: Context, amount: int) -> None: + + await context.send("Deleting messages...") # Bit of a hacky way to make sure the bot responds to the interaction and doens't get a "Unknown Interaction" response + purged_messages = await context.channel.purge(limit=amount+1) + embed = discord.Embed( + title="Chat Cleared!", + description=f"**{context.author}** cleared **{len(purged_messages)-1}** messages!", + color=0x9C84EF + ) + await context.channel.send(embed=embed) + + @commands.hybrid_command( + name="hackban", + description="Bans a user without the user having to be in the server.", + ) + @commands.has_permissions(ban_members=True) + @commands.bot_has_permissions(ban_members=True) + @checks.not_blacklisted() + @app_commands.describe(user_id="The user ID that should be banned.", reason="The reason why the user should be banned.") + async def hackban(self, context: Context, user_id: str, *, reason: str = "Not specified") -> None: + + try: + await self.bot.http.ban(user_id, context.guild.id, reason=reason) + user = self.bot.get_user(int(user_id)) or await self.bot.fetch_user(int(user_id)) + embed = discord.Embed( + title="User Banned!", + description=f"**{user} (ID: {user_id}) ** was banned by **{context.author}**!", + color=0x9C84EF + ) + embed.add_field( + name="Reason:", + value=reason + ) + await context.send(embed=embed) + except Exception as e: + embed = discord.Embed( + title="Error!", + description="An error occurred while trying to ban the user. Make sure ID is an existing ID that belongs to a user.", + color=0xE02B2B + ) + await context.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(Moderation(bot)) \ No newline at end of file diff --git a/cogs/neko.py b/cogs/neko.py new file mode 100644 index 0000000..c9eae2e --- /dev/null +++ b/cogs/neko.py @@ -0,0 +1,628 @@ +import nekos +import urllib + + + +import discord +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + + +# from nekos import img +from nekos import * + +from helpers import checks, db_manager + + +class Neko(commands.Cog, name="neko"): + def __init__(self, bot): + self.bot = bot + def img(target: str): + target == [ + "wallpaper", + "ngif", + "tickle", + #"lewd", Provides 1 image + "feed", + "gecg", + "gasm", + "slap", + "avatar", + "lizard", + "waifu", + "pat", + "8ball", + "kiss", + "neko", + "spank", + "cuddle", + "fox_girl", + "hug", + "smug", + "goose", + "woof", + ] +#------------------------------------------------------------------------------- + + @commands.hybrid_group( + name="nekos", + description="All nekos commands.", + ) + @checks.not_blacklisted() + async def nekos(self, context: Context) -> None: + + if context.invoked_subcommand is None: + embed = discord.Embed( + title="All targets for nekos api", + description="Please specify a target.\n\n`wallpaper`, `ngif`, `tickle`, `feed`, `gecg`, `gasm`, `slap`, `avatar`, `lizard`, `waifu`, `pat`, `8ball`, `kiss`, `neko`, `spank`, `cuddle`, `fox_girl`, `hug`, `smug`, `goose`, `woof`", + color=0xE02B2B + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + base="nekos", + name="wallpaper", + description="Send a Random Anime image in channel.(NSFW included)", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def wallpaper(self, context: commands.Context, target: str = "Wallpaper") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos Wallpaper", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="ngif", + description="Ramdom ngif.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def ngif(self, context: commands.Context, target: str = "ngif") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos Wallpaper", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="tickle", + description="Ramdom tickle.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + @app_commands.describe(user="The user that that you want to tickle") + async def tickle(self, context: commands.Context, user: discord.User, target: str = "tickle") -> None: + + url = (nekos.img(target)) + data = (user.mention) + embed = discord.Embed( + title="Nekos tickle", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(data) + await context.send(embed=embed) +#------------------------------------------------------------------------------- + + @nekos.command( + name="feed", + description="Ramdom feed.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def feed(self, context: commands.Context, target: str = "feed") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos feed", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="gecg", + description="Ramdom gecg.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def gecg(self, context: commands.Context, target: str = "gecg") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos gecg", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="gasm", + description="Ramdom gasm.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def gasm(self, context: commands.Context, target: str = "gasm") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos gasm", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="slap", + description="Ramdom Anime slap.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def slap(self, context: commands.Context, target: str = "slap") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos slap", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="avatar", + description="Ramdom avatar.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def avatar(self, context: commands.Context, target: str = "avatar") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos avatar", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="lizard", + description="Ramdom lizard.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def lizard(self, context: commands.Context, target: str = "lizard") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos lizard", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="waifu", + description="Ramdom Anime waifu.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def waifu(self, context: commands.Context, target: str = "waifu") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos waifu", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="pat", + description="Ramdom pat.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def pat(self, context: commands.Context, target: str = "pat") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos pat", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="eightball", + description="Ramdom eightball.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def eightball(self, context: commands.Context, target: str = "8ball") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos eightball", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="neko", + description="Ramdom neko.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def neko(self, context: commands.Context, target: str = "neko") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos neko", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="kiss", + description="Ramdom kiss.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def kiss(self, context: commands.Context, target: str = "kiss") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos kiss", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="spank", + description="Ramdom spank.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def spank(self, context: commands.Context, target: str = "spank") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos spank", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="cuddle", + description="Ramdom cuddle.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def cuddle(self, context: commands.Context, target: str = "cuddle") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos cuddle", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="fox_girl", + description="Ramdom fox_girl.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def fox_girl(self, context: commands.Context, target: str = "fox_girl") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos fox_girl", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="hug", + description="Ramdom hug.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def hug(self, context: commands.Context, target: str = "hug") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos hug", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="smug", + description="Ramdom smug.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def smug(self, context: commands.Context, target: str = "smug") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos smug", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="goose", + description="Ramdom goose.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def goose(self, context: commands.Context, target: str = "goose") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos goose", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @nekos.command( + name="woof", + description="Ramdom woof.", + ) + @checks.not_blacklisted() + @app_commands.describe(target="This can be left blank") + async def woof(self, context: commands.Context, target: str = "woof") -> None: + + url = (nekos.img(target)) + embed = discord.Embed( + title="Nekos woof", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + + + @commands.hybrid_command( + name="cat", + description="Send a random cat image", + ) + @checks.not_blacklisted() + async def cat(self, context: Context) -> None: + url = (nekos.cat()) + embed = discord.Embed( + title="Nekos Cat", + description=f"{context.author}", + color=0x9C84EF + ) + embed.set_image( + url=url + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="name", + description="Ramdom Name Text.", + ) + @checks.not_blacklisted() + async def name(self, context: Context) -> None: + description = (nekos.name()) + embed = discord.Embed( + title="Nekos Cat", + description=description, + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="why", + description="Why.", + ) + @checks.not_blacklisted() + async def why(self, context: Context) -> None: + description = (nekos.why()) + embed = discord.Embed( + title="Nekos Why", + description=description, + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="nfact", + description="Returns a random Fact.", + ) + @checks.not_blacklisted() + async def nfact(self, context: Context) -> None: + description = (nekos.fact()) + embed = discord.Embed( + title="Nekos fact", + description=description, + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="textcat", + description="Ramdom Name Textcat.", + ) + @checks.not_blacklisted() + async def textcat(self, context: Context) -> None: + description = (nekos.textcat()) + embed = discord.Embed( + title="Nekos textcat", + description=description, + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="owoify", + description="Ramdom Name Textcat.", + ) + @checks.not_blacklisted() + async def owoify(self, context: Context, text: str) -> None: + description = (nekos.owoify(text)) + embed = discord.Embed( + title="Nekos owoify", + description=description, + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="lewd", + description="Ramdom lewd.", + ) + @checks.not_blacklisted() + async def lewd(self, context: Context, target: str = 'lewd') -> None: + description = (nekos.img(target)) + embed = discord.Embed( + title="Nekos lewd", + description=description, + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + +async def setup(bot): + await bot.add_cog(Neko(bot)) diff --git a/cogs/owner.py b/cogs/owner.py new file mode 100644 index 0000000..906dd96 --- /dev/null +++ b/cogs/owner.py @@ -0,0 +1,353 @@ +import discord +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context + +from helpers import checks, db_manager + + +class Owner(commands.Cog, name="owner"): + def __init__(self, bot): + self.bot = bot + +#------------------------------------------------------------------------------- + + @commands.command( + name="sync", + description="Synchonizes the slash commands.", + ) + @app_commands.describe(scope="The scope of the sync. Can be `global` or `guild`") + @checks.is_owner() + async def sync(self, context: Context, scope: str) -> None: + if scope == "global": + await context.bot.tree.sync() + embed = discord.Embed( + title="Slash Commands Sync", + description="Slash commands have been globally synchronized.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + elif scope == "guild": + context.bot.tree.copy_global_to(guild=context.guild) + await context.bot.tree.sync(guild=context.guild) + embed = discord.Embed( + title="Slash Commands Sync", + description="Slash commands have been synchronized in this guild.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Invalid Scope", + description="The scope must be `global` or `guild`.", + color=0xE02B2B + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.command( + name="unsync", + description="Unsynchonizes the slash commands.", + ) + @app_commands.describe(scope="The scope of the sync. Can be `global`, `current_guild` or `guild`") + @checks.is_owner() + async def unsync(self, context: Context, scope: str) -> None: + if scope == "global": + context.bot.tree.clear_commands(guild=None) + await context.bot.tree.sync() + embed = discord.Embed( + title="Slash Commands Unsync", + description="Slash commands have been globally unsynchronized.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + elif scope == "guild": + context.bot.tree.clear_commands(guild=context.guild) + await context.bot.tree.sync(guild=context.guild) + embed = discord.Embed( + title="Slash Commands Unsync", + description="Slash commands have been unsynchronized in this guild.", + color=0x9C84EF + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Invalid Scope", + description="The scope must be `global` or `guild`.", + color=0xE02B2B + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="load", + description="Load a cog", + ) + @app_commands.describe(cog="The name of the cog to load") + @checks.is_owner() + async def load(self, context: Context, cog: str) -> None: + + try: + await self.bot.load_extension(f"cogs.{cog}") + except Exception: + embed = discord.Embed( + title="Error!", + description=f"Could not load the `{cog}` cog.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Load", + description=f"Successfully loaded the `{cog}` cog.", + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="unload", + description="Unloads a cog.", + ) + @app_commands.describe(cog="The name of the cog to unload") + @checks.is_owner() + async def unload(self, context: Context, cog: str) -> None: + + try: + await self.bot.unload_extension(f"cogs.{cog}") + except Exception: + embed = discord.Embed( + title="Error!", + description=f"Could not unload the `{cog}` cog.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Unload", + description=f"Successfully unloaded the `{cog}` cog.", + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="reload", + description="Reloads a cog.", + ) + @app_commands.describe(cog="The name of the cog to reload") + @checks.is_owner() + async def reload(self, context: Context, cog: str) -> None: + + try: + await self.bot.reload_extension(f"cogs.{cog}") + except Exception: + embed = discord.Embed( + title="Error!", + description=f"Could not reload the `{cog}` cog.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + embed = discord.Embed( + title="Reload", + description=f"Successfully reloaded the `{cog}` cog.", + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="shutdown", + description="Make the bot shutdown.", + ) + @checks.is_owner() + async def shutdown(self, context: Context) -> None: + + embed = discord.Embed( + description="Shutting down. Bye! :wave:", + color=0x9C84EF + ) + await context.send(embed=embed) + await self.bot.close() + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="say", + description="The bot will say anything you want.", + ) + @app_commands.describe(message="The message that should be repeated by the bot") + @checks.is_owner() + async def say(self, context: Context, *, message: str) -> None: + + await context.send(message) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="embed", + description="The bot will say anything you want, but within embeds.", + ) + @app_commands.describe(message="The message that should be repeated by the bot") + @checks.is_owner() + async def embed(self, context: Context, *, message: str) -> None: + + embed = discord.Embed( + description=message, + color=0x9C84EF + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_group( + name="blacklist", + description="Get the list of all blacklisted users.", + ) + @checks.is_owner() + async def blacklist(self, context: Context) -> None: + + if context.invoked_subcommand is None: + embed = discord.Embed( + title="Blacklist", + description="You need to specify a subcommand.\n\n**Subcommands:**\n`add` - Add a user to the blacklist.\n`remove` - Remove a user from the blacklist.", + color=0xE02B2B + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @blacklist.command( + base="blacklist", + name="show", + description="Shows the list of all blacklisted users.", + ) + @checks.is_owner() + async def blacklist_show(self, context: Context) -> None: + + blacklisted_users = await db_manager.get_blacklisted_users() + if len(blacklisted_users) == 0: + embed = discord.Embed( + description="There are currently no blacklisted users.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + + embed = discord.Embed( + title="Blacklisted users", + color=0x9C84EF + ) + users = [] + for bluser in blacklisted_users: + user = self.bot.get_user(int(bluser[0])) or await self.bot.fetch_user(int(bluser[0])) + users.append( + f"โ€ข {user.mention} ({user}) - Blacklisted ") + embed.description = "\n".join(users) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @blacklist.command( + base="blacklist", + name="add", + description="Lets you add a user from not being able to use the bot.", + ) + @app_commands.describe(user="The user that should be added to the blacklist") + @checks.is_owner() + async def blacklist_add(self, context: Context, user: discord.User) -> None: + + user_id = user.id + if await db_manager.is_blacklisted(user_id): + embed = discord.Embed( + title="Error!", + description=f"**{user.name}** is already in the blacklist.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + total = await db_manager.add_user_to_blacklist(user_id) + embed = discord.Embed( + title="User Blacklisted", + description=f"**{user.name}** has been successfully added to the blacklist", + color=0x9C84EF + ) + embed.set_footer( + text=f"There {'is' if total == 1 else 'are'} now {total} {'user' if total == 1 else 'users'} in the blacklist" + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @blacklist.command( + base="blacklist", + name="remove", + description="Lets you remove a user from not being able to use the bot.", + ) + @app_commands.describe(user="The user that should be removed from the blacklist.") + @checks.is_owner() + async def blacklist_remove(self, context: Context, user: discord.User) -> None: + + user_id = user.id + if not await db_manager.is_blacklisted(user_id): + embed = discord.Embed( + title="Error!", + description=f"**{user.name}** is not in the blacklist.", + color=0xE02B2B + ) + await context.send(embed=embed) + return + total = await db_manager.remove_user_from_blacklist(user_id) + embed = discord.Embed( + title="User removed from blacklist", + description=f"**{user.name}** has been successfully removed from the blacklist", + color=0x9C84EF + ) + embed.set_footer( + text=f"There {'is' if total == 1 else 'are'} now {total} {'user' if total == 1 else 'users'} in the blacklist" + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + @commands.hybrid_command( + name="leave", + description="Lets you remove a the bot from a server.", + ) + @checks.is_owner() + @app_commands.describe(guild_id="Supply a guild ID.") + async def leave(self, context, guild_id, reason: str = "This BOT is not allowed to be on other Guild other that the guild it was made for") -> None: + + try: + await self.bot.get_guild(int(guild_id)).leave() + embed = discord.Embed( + title="BOT Removed frome guild", + description=f"I left: {guild_id}", + color=0x9C84EF + ) + embed.add_field( + name="Reason:", + value=reason + ) + await context.send(embed=embed) + except Exception as e: + embed = discord.Embed( + title="Error!", + description="An error occurred while trying to remove the bot. Make sure GUILD_ID is an existing ID.", + color=0xE02B2B + ) + await context.send(embed=embed) + +#------------------------------------------------------------------------------- + + +async def setup(bot): + await bot.add_cog(Owner(bot)) \ No newline at end of file diff --git a/cogs/template.py b/cogs/template.py new file mode 100644 index 0000000..1e1b711 --- /dev/null +++ b/cogs/template.py @@ -0,0 +1,35 @@ +from discord.ext import commands +from discord.ext.commands import Context + +from helpers import checks + + +# Here we name the cog and create a new class for the cog. +class Template(commands.Cog, name="template"): + def __init__(self, bot): + self.bot = bot + + # Here you can just add your own commands, you'll always need to provide "self" as first parameter. + + @commands.hybrid_command( + name="testcommand", + description="This is a testing command that does nothing.", + ) + # This will only allow non-blacklisted members to execute the command + @checks.not_blacklisted() + # This will only allow owners of the bot to execute the command -> config.json + @checks.is_owner() + async def testcommand(self, context: Context): + """ + This is a testing command that does nothing. + :param context: The application command context. + """ + # Do your stuff here + + # Don't forget to remove "pass", I added this just because there's no content in the method. + pass + + +# And then we finally add the cog to the bot so that it can load, unload, reload and use it's content. +async def setup(bot): + await bot.add_cog(Template(bot)) \ No newline at end of file diff --git a/cogs/weather.py b/cogs/weather.py new file mode 100644 index 0000000..33f201a --- /dev/null +++ b/cogs/weather.py @@ -0,0 +1,264 @@ +import asyncio +import html +import json +import random +from time import time +from typing import Annotated, Any, Optional +from zoneinfo import ZoneInfo + +import discord +from discord import app_commands +from discord.ext import commands +from discord.ext.commands import Context +from discord.utils import get + +from discord.utils import * + +from helpers import checks, db_manager + +# This is the Weather module +class Weather(commands.Cog, name="weather"): + + def __init__(self, bot): + self.bot = bot + with open("data/weather.json") as f: + self.weather_constants = json.load(f) + + + @commands.group() + async def weather(self, ctx: commands.Context): + """Show current weather in given location""" + if ctx.invoked_subcommand is None: + await util.command_group_help(ctx) + else: + ctx.location = await self.bot.db.fetch_value( + "SELECT location_string FROM user_settings WHERE user_id = %s", + ctx.author.id, + ) + + @weather.command(name="now") + async def weather_now(self, ctx: commands.Context, *, location: Optional[str] = None): + if location is None: + location = ctx.location + if ctx.location is None: + raise exceptions.CommandInfo( + f"Please save your location using `{ctx.prefix}weather save `" + ) + + lat, lon, address = await self.geolocate(location) + local_time, country_code = await self.get_country_information(lat, lon) + + API_BASE_URL = "https://api.tomorrow.io/v4/timelines" + params = { + "apikey": self.bot.keychain.TOMORROWIO_TOKEN, + "location": f"{lat},{lon}", + "fields": ",".join( + [ + "precipitationProbability", + "precipitationType", + "windSpeed", + "windGust", + "windDirection", + "temperature", + "temperatureApparent", + "cloudCover", + "weatherCode", + "humidity", + "temperatureMin", + "temperatureMax", + "sunriseTime", + "sunsetTime", + ] + ), + "units": "metric", + "timesteps": ",".join(["current", "1d"]), + "endTime": arrow.utcnow().shift(days=+1, minutes=+5).isoformat(), + } + + if isinstance(local_time.tzinfo, ZoneInfo): + params["timezone"] = str(local_time.tzinfo) + else: + logger.warning("Arrow object must be constructed with ZoneInfo timezone object") + + async with self.bot.session.get(API_BASE_URL, params=params) as response: + if response.status != 200: + logger.error(response.status) + logger.error(response.headers) + logger.error(await response.text()) + raise exceptions.CommandError(f"Weather api returned HTTP ERROR {response.status}") + data = await response.json(loads=orjson.loads) + + current_data = next( + filter(lambda t: t["timestep"] == "current", data["data"]["timelines"]) + ) + daily_data = next(filter(lambda t: t["timestep"] == "1d", data["data"]["timelines"])) + values_current = current_data["intervals"][0]["values"] + values_today = daily_data["intervals"][0]["values"] + # values_tomorrow = daily_data["intervals"][1]["values"] + temperature = values_current["temperature"] + temperature_apparent = values_current["temperatureApparent"] + sunrise = arrow.get(values_current["sunriseTime"]).to(local_time.tzinfo).format("HH:mm") + sunset = arrow.get(values_current["sunsetTime"]).to(local_time.tzinfo).format("HH:mm") + + icon = self.weather_constants["id_to_icon"][str(values_current["weatherCode"])] + summary = self.weather_constants["id_to_description"][str(values_current["weatherCode"])] + + if ( + values_today["precipitationType"] != 0 + and values_today["precipitationProbability"] != 0 + ): + precipitation_type = self.weather_constants["precipitation"][ + str(values_today["precipitationType"]) + ] + summary += f", with {values_today['precipitationProbability']}% chance of {precipitation_type}" + + content = discord.Embed(color=int("e1e8ed", 16)) + content.title = f":flag_{country_code.lower()}: {address}" + content.set_footer(text=f"๐Ÿ• Local time {local_time.format('HH:mm')}") + + def render(F: bool): + information_rows = [ + f":thermometer: Currently **{temp(temperature, F)}**, feels like **{temp(temperature_apparent, F)}**", + f":calendar: Daily low **{temp(values_today['temperatureMin'], F)}**, high **{temp(values_today['temperatureMax'], F)}**", + f":dash: Wind speed **{values_current['windSpeed']} m/s** with gusts of **{values_current['windGust']} m/s**", + f":sunrise: Sunrise at **{sunrise}**, sunset at **{sunset}**", + f":sweat_drops: Air humidity **{values_current['humidity']}%**", + f":map: [See on map](https://www.google.com/maps/search/?api=1&query={lat},{lon})", + ] + + content.clear_fields().add_field( + name=f"{icon} {summary}", + value="\n".join(information_rows), + ) + + return content + + await WeatherUnitToggler(render, False).run(ctx) + + @weather.command(name="forecast") + async def weather_forecast(self, ctx: commands.Context, *, location: Optional[str] = None): + if location is None: + location = ctx.location + if ctx.location is None: + raise exceptions.CommandInfo( + f"Please save your location using `{ctx.prefix}weather save `" + ) + + lat, lon, address = await self.geolocate(location) + local_time, country_code = await self.get_country_information(lat, lon) + API_BASE_URL = "https://api.tomorrow.io/v4/timelines" + params = { + "apikey": self.bot.keychain.TOMORROWIO_TOKEN, + "location": f"{lat},{lon}", + "fields": ",".join( + [ + "precipitationProbability", + "precipitationType", + "windSpeed", + "windGust", + "windDirection", + "temperature", + "temperatureApparent", + "cloudCover", + "weatherCode", + "humidity", + "temperatureMin", + "temperatureMax", + ] + ), + "units": "metric", + "timesteps": "1d", + "endTime": arrow.utcnow().shift(days=+7).isoformat(), + } + + if isinstance(local_time.tzinfo, ZoneInfo): + params["timezone"] = str(local_time.tzinfo) + else: + logger.warning("Arrow object must be constructed with ZoneInfo timezone object") + + async with self.bot.session.get(API_BASE_URL, params=params) as response: + if response.status != 200: + logger.error(response.status) + logger.error(response.headers) + logger.error(await response.text()) + raise exceptions.CommandError(f"Weather api returned HTTP ERROR {response.status}") + data = await response.json(loads=orjson.loads) + + content = discord.Embed( + title=f":flag_{country_code.lower()}: {address}", + color=int("ffcc4d", 16), + ) + + def render(F: bool): + days = [] + for day in data["data"]["timelines"][0]["intervals"]: + date = arrow.get(day["startTime"]).format("**`ddd`** `D/M`") + values = day["values"] + minTemp = values["temperatureMin"] + maxTemp = values["temperatureMax"] + icon = self.weather_constants["id_to_icon"][str(values["weatherCode"])] + description = self.weather_constants["id_to_description"][ + str(values["weatherCode"]) + ] + days.append( + f"{date} {icon} **{temp(maxTemp, F)}** / **{temp(minTemp, F)}** โ€” {description}" + ) + + content.description = "\n".join(days) + return content + + await WeatherUnitToggler(render, False).run(ctx) + + @weather.command(name="save") + async def weather_save(self, ctx: commands.Context, *, location: str): + await self.bot.db.execute( + """ + INSERT INTO user_settings (user_id, location_string) + VALUES (%s, %s) + ON DUPLICATE KEY UPDATE + location_string = VALUES(location_string) + """, + ctx.author.id, + location, + ) + return await util.send_success(ctx, f"Saved your location as `{location}`") + + async def geolocate(self, location): + GOOGLE_GEOCODING_API_URL = "https://maps.googleapis.com/maps/api/geocode/json" + params = {"address": location, "key": self.bot.keychain.GCS_DEVELOPER_KEY} + async with self.bot.session.get(GOOGLE_GEOCODING_API_URL, params=params) as response: + geocode_data = await response.json(loads=orjson.loads) + try: + geocode_data = geocode_data["results"][0] + except IndexError: + raise exceptions.CommandWarning("Could not find that location!") + + formatted_name = geocode_data["formatted_address"] + lat = geocode_data["geometry"]["location"]["lat"] + lon = geocode_data["geometry"]["location"]["lng"] + + return lat, lon, formatted_name + + async def get_country_information(self, lat, lon): + TIMEZONE_API_URL = "http://api.timezonedb.com/v2.1/get-time-zone" + params = { + "key": self.bot.keychain.TIMEZONEDB_API_KEY, + "format": "json", + "by": "position", + "lat": lat, + "lng": lon, + } + async with self.bot.session.get(TIMEZONE_API_URL, params=params) as response: + data = await response.json(loads=orjson.loads) + country_code = data["countryCode"] + try: + local_time = arrow.now(ZoneInfo(data["zoneName"])) + except ValueError: + # does not have a time zone + # most likely a special place such as antarctica + # use UTC + local_time = arrow.utcnow() + return local_time, country_code + +async def setup(bot): + await bot.add_cog(Weather(bot)) \ No newline at end of file diff --git a/database/schema.sql b/database/schema.sql new file mode 100644 index 0000000..0af24cb --- /dev/null +++ b/database/schema.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS `blacklist` ( + `user_id` varchar(20) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS `warns` ( + `id` int(11) NOT NULL, + `user_id` varchar(20) NOT NULL, + `server_id` varchar(20) NOT NULL, + `moderator_id` varchar(20) NOT NULL, + `reason` varchar(255) NOT NULL, + `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP +); \ No newline at end of file diff --git a/exceptions/__init__.py b/exceptions/__init__.py new file mode 100644 index 0000000..38fd778 --- /dev/null +++ b/exceptions/__init__.py @@ -0,0 +1,13 @@ +from discord.ext import commands + + +class UserBlacklisted(commands.CheckFailure): + def __init__(self, message="User is blacklisted!"): + self.message = message + super().__init__(self.message) + + +class UserNotOwner(commands.CheckFailure): + def __init__(self, message="User is not an owner of the bot!"): + self.message = message + super().__init__(self.message) \ No newline at end of file diff --git a/helpers/checks.py b/helpers/checks.py new file mode 100644 index 0000000..7e966cc --- /dev/null +++ b/helpers/checks.py @@ -0,0 +1,30 @@ +import json +import os +from typing import Callable, TypeVar + +from discord.ext import commands + +from exceptions import * +from helpers import db_manager + +T = TypeVar("T") + + +def is_owner() -> Callable[[T], T]: + async def predicate(context: commands.Context) -> bool: + with open(f"{os.path.realpath(os.path.dirname(__file__))}/../config.json") as file: + data = json.load(file) + if context.author.id not in data["owners"]: + raise UserNotOwner + return True + + return commands.check(predicate) + + +def not_blacklisted() -> Callable[[T], T]: + async def predicate(context: commands.Context) -> bool: + if await db_manager.is_blacklisted(context.author.id): + raise UserBlacklisted + return True + + return commands.check(predicate) \ No newline at end of file diff --git a/helpers/db_manager.py b/helpers/db_manager.py new file mode 100644 index 0000000..9ce910b --- /dev/null +++ b/helpers/db_manager.py @@ -0,0 +1,106 @@ +import os + +import aiosqlite + +DATABASE_PATH = f"{os.path.realpath(os.path.dirname(__file__))}/../database/database.db" + + +async def get_blacklisted_users() -> list: + """ + This function will return the list of all blacklisted users. + :param user_id: The ID of the user that should be checked. + :return: True if the user is blacklisted, False if not. + """ + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute("SELECT user_id, strftime('%s', created_at) FROM blacklist") as cursor: + result = await cursor.fetchall() + return result + + +async def is_blacklisted(user_id: int) -> bool: + """ + This function will check if a user is blacklisted. + :param user_id: The ID of the user that should be checked. + :return: True if the user is blacklisted, False if not. + """ + async with aiosqlite.connect(DATABASE_PATH) as db: + async with db.execute("SELECT * FROM blacklist WHERE user_id=?", (user_id,)) as cursor: + result = await cursor.fetchone() + return result is not None + + +async def add_user_to_blacklist(user_id: int) -> int: + """ + This function will add a user based on its ID in the blacklist. + :param user_id: The ID of the user that should be added into the blacklist. + """ + async with aiosqlite.connect(DATABASE_PATH) as db: + await db.execute("INSERT INTO blacklist(user_id) VALUES (?)", (user_id,)) + await db.commit() + rows = await db.execute("SELECT COUNT(*) FROM blacklist") + async with rows as cursor: + result = await cursor.fetchone() + return result[0] if result is not None else 0 + + +async def remove_user_from_blacklist(user_id: int) -> int: + """ + This function will remove a user based on its ID from the blacklist. + :param user_id: The ID of the user that should be removed from the blacklist. + """ + async with aiosqlite.connect(DATABASE_PATH) as db: + await db.execute("DELETE FROM blacklist WHERE user_id=?", (user_id,)) + await db.commit() + rows = await db.execute("SELECT COUNT(*) FROM blacklist") + async with rows as cursor: + result = await cursor.fetchone() + return result[0] if result is not None else 0 + + +async def add_warn(user_id: int, server_id: int, moderator_id: int, reason: str) -> int: + """ + This function will add a warn to the database. + :param user_id: The ID of the user that should be warned. + :param reason: The reason why the user should be warned. + """ + async with aiosqlite.connect(DATABASE_PATH) as db: + rows = await db.execute("SELECT id FROM warns WHERE user_id=? AND server_id=? ORDER BY id DESC LIMIT 1", (user_id, server_id,)) + async with rows as cursor: + result = await cursor.fetchone() + warn_id = result[0] + 1 if result is not None else 1 + await db.execute("INSERT INTO warns(id, user_id, server_id, moderator_id, reason) VALUES (?, ?, ?, ?, ?)", (warn_id, user_id, server_id, moderator_id, reason,)) + await db.commit() + return warn_id + + +async def remove_warn(warn_id: int, user_id: int, server_id: int) -> int: + """ + This function will remove a warn from the database. + :param warn_id: The ID of the warn. + :param user_id: The ID of the user that was warned. + :param server_id: The ID of the server where the user has been warned + """ + async with aiosqlite.connect(DATABASE_PATH) as db: + await db.execute("DELETE FROM warns WHERE id=? AND user_id=? AND server_id=?", (warn_id, user_id, server_id,)) + await db.commit() + rows = await db.execute("SELECT COUNT(*) FROM warns WHERE user_id=? AND server_id=?", (user_id, server_id,)) + async with rows as cursor: + result = await cursor.fetchone() + return result[0] if result is not None else 0 + + +async def get_warnings(user_id: int, server_id: int) -> list: + """ + This function will get all the warnings of a user. + :param user_id: The ID of the user that should be checked. + :param server_id: The ID of the server that should be checked. + :return: A list of all the warnings of the user. + """ + async with aiosqlite.connect(DATABASE_PATH) as db: + rows = await db.execute("SELECT user_id, server_id, moderator_id, reason, strftime('%s', created_at), id FROM warns WHERE user_id=? AND server_id=?", (user_id, server_id,)) + async with rows as cursor: + result = await cursor.fetchall() + result_list = [] + for row in result: + result_list.append(row) + return result_list \ No newline at end of file diff --git a/install.py b/install.py new file mode 100644 index 0000000..5769456 --- /dev/null +++ b/install.py @@ -0,0 +1,21 @@ +import os + + +os.system("pip3 install asyncio") +os.system("pip3 install aiohttp") +os.system("pip3 install aiosqlite") +os.system("pip3 install discord.py") +os.system("pip3 install discord_webhook") +os.system("pip3 install requests") +os.system("pip3 install nekos.py") +os.system("pip3 install psutil") +os.system("pip3 install pyparsing") +os.system("pip3 install pyquery") +os.system("pip3 install numpy") +os.system("pip3 install igdb_api_python") +os.system("pip3 install geopy") +os.system("pip3 install pyfiglet") +os.system("pip3 install flake8") + + +exec(open("main.py").read()) \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..cda3676 --- /dev/null +++ b/main.py @@ -0,0 +1,4 @@ +import bot + +if __name__ == '__main__': + bot.run_discord_bot() \ No newline at end of file