diff --git a/bot.py b/bot.py index e69de29..34f5c0a 100644 --- a/bot.py +++ b/bot.py @@ -0,0 +1,566 @@ +import asyncio +import json +import os +import os.path +import platform +import random +import sys +import aiohttp +import aiosqlite + +import random +from random import randint + +import requests +from requests import get + +import time +import datetime +from datetime import datetime, timedelta, timezone + +import discord +from discord.ext import commands, tasks +from discord.ext.commands import Bot, Context +from discord.utils import get + +import exceptions + +from helpers import checks + +# --------------------------------------------------------------------------------------- +# Loads config.json +# --------------------------------------------------------------------------------------- +if not os.path.isfile("config.json"): + sys.exit("'config.json' not found! Please add it and try again.") +else: + with open("config.json") as file: + config = json.load(file) + +# --------------------------------------------------------------------------------------- +# Intents Listing +# --------------------------------------------------------------------------------------- +intents = discord.Intents.all() +intents.members = True +intents.message_content = True +intents.messages = True # `message_content` is required to get the content of the messages +intents.presences = True +intents.bans = True +intents.dm_messages = True +intents.dm_reactions = True +intents.dm_typing = True +intents.emojis = True +intents.emojis_and_stickers = True +intents.guild_messages = True +intents.guild_reactions = True +intents.guild_scheduled_events = True +intents.guild_typing = True +intents.guilds = True +intents.integrations = True +intents.invites = True +intents.reactions = True +intents.typing = True +intents.voice_states = True +intents.webhooks = True + +# --------------------------------------------------------------------------------------- +# Def what happend on mention or msg +# --------------------------------------------------------------------------------------- +bot = Bot(command_prefix=commands.when_mentioned_or( + config["prefix"]), intents=intents, help_command=None) +bot.config = config + +# --------------------------------------------------------------------------------------- +# DB Set +# --------------------------------------------------------------------------------------- + +async def init_db(): + async with aiosqlite.connect(f"{os.path.realpath(os.path.dirname(__file__))}/database/database.db") as db: + with open(f"{os.path.realpath(os.path.dirname(__file__))}/database/schema.sql") as file: + await db.executescript(file.read()) + await db.commit() + +# --------------------------------------------------------------------------------------- +# Webhook Logs +# --------------------------------------------------------------------------------------- +url = config["webhook"] + +def weblogsend(msg): + data = { + "content" : msg, + "username": {context.author} + } + result = requests.post(url, json = data) + try: + result.raise_for_status() + except requests.exceptions.HTTPError as err: + print(err) + else: + print("Payload delivered successfully, code {}.".format(result.status_code)) + +# --------------------------------------------------------------------------------------- +# Bot Ready Start +# --------------------------------------------------------------------------------------- +@bot.event +async def on_ready() -> None: + guilds = [guild.id for guild in bot.guilds] + print("--------------------------------------") + print(f"Logged in as {bot.user.name}") + print(f"discord.py API version: {discord.__version__}") + print(f"Python version: {platform.python_version()}") + print(f"Running on: {platform.system()} {platform.release()} ({os.name})") + print("--------------------------------------") + print(f"The {bot.user.name} bot is in {len(guilds)} Guilds.\nThe guilds ids list : {guilds}") + print("--------------------------------------") + status_task.start() + if config["sync_commands_globally"]: + print("Syncing commands globally...") + await bot.tree.sync() + +# --------------------------------------------------------------------------------------- +# Presence Loop +# --------------------------------------------------------------------------------------- +@tasks.loop(minutes=5) +async def status_task() -> None: + activity = discord.Activity(name=f"over {len(bot.guilds)} guilds\nand {len(bot.users)} users!", type=discord.ActivityType.watching) + await bot.change_presence(activity=activity) + +# --------------------------------------------------------------------------------------- +# User joins guild events +# --------------------------------------------------------------------------------------- +@bot.event +async def on_member_join(member) -> None: + role = member.guild.get_role(1061402091542806648) # + await member.add_roles(role, reason="Automatic role by bot") + embed = ( + (f"|| <@&MENTION_ROLE_ID> ||\nHello {member.mention}") # MENTION_ROLE_ID = ID of role the will be mentioned on [member.guild_join] + ) + discord.Embed( + description=(f"Welcome to 🐱‍🚀{member.guild.name}!\nHope you enjoy your time here."), + color=0x9C84EF + ) + embed.set_author( + name="Welcome" + ) + embed.add_field( + name="Embed Field Name", + value="`➖➖➖➖➖➖➖➖➖`\n> **Frostbite Hosting**\n`🔗` [DISCORD SERVER]()\n`➖➖➖➖➖➖➖➖➖`\n> **Frostbite Hosting**\n`🔗` [Free Bot Hosting]()\n`➖➖➖➖➖➖➖➖➖`", + inline=False + ) + channel = await bot.fetch_channel(GUILD_WELCOME_CHANNEL_ID) # Replace GUILD_WELCOME_CHANNEL_ID with to the channel ID where it should announce the embed. + await channel.send(embed=embed) + await member.send(embed=embed) + +# --------------------------------------------------------------------------------------- +# The code in this event is executed every time someone sends a message, with or without the prefix +# --------------------------------------------------------------------------------------- +@bot.event +async def on_message(message: discord.Message): + + if message.author.id == bot.user.id or message.author.bot: + return + if not message.guild: + logchannel = bot.get_guild(guild_id).get_channel(channel_id) # Define the log channel when someone DM's the bot with [guild ID] and [channel ID] + embed = discord.Embed( + title="User MSG BOT DMs ", + colour=0x000, + description=f"**From:** {message.author.mention}\n\n*{message.content}*" + ) + embed.set_footer( + text=message.author, + icon_url=message.author.avatar.url + ) + await logchannel.send(embed=embed) + await bot.process_commands(message) + +# --------------------------------------------------------------------------------------- +# Error handling +# This is global error handling, if you for instance want to except every CommandOnCooldown exception, you can do it like this +# --------------------------------------------------------------------------------------- +# on_command_completion +# --------------------------------------------------------------------------------------- +@bot.event +async def on_command_completion(context: Context) -> None: + + full_command_name = context.command.qualified_name + split = full_command_name.split(" ") + executed_command = str(split[0]) + if context.guild is not None: + print( + f"Executed {executed_command} command in {context.guild.name} (ID: {context.guild.id}) by {context.author} (ID: {context.author.id})") + else: + print( + f"Executed {executed_command} command by {context.author} (ID: {context.author.id}) in DMs") + +# --------------------------------------------------------------------------------------- +# on_command_error +# --------------------------------------------------------------------------------------- +@bot.event +async def on_command_error(context: Context, error) -> None: + + if isinstance(error, commands.CommandOnCooldown): + minutes, seconds = divmod(error.retry_after, 60) + hours, minutes = divmod(minutes, 60) + hours = hours % 24 + embed = discord.Embed( + title="Hey, Stop that . you will break me", + description=f"You can use this command again in {f'{round(hours)} hours' if round(hours) > 0 else ''} {f'{round(minutes)} minutes' if round(minutes) > 0 else ''} {f'{round(seconds)} seconds' if round(seconds) > 0 else ''}.", + color=0xE02B2B + ) + await context.send(embed=embed) + elif isinstance(error, exceptions.UserBlacklisted): + embed = discord.Embed( + title="Error!", + description="You are blacklisted from using the bot.", + color=0xE02B2B + ) + await context.send(embed=embed) + elif isinstance(error, exceptions.UserNotOwner): + embed = discord.Embed( + title="Error!", + description="You are not the owner of the bot!", + color=0xE02B2B + ) + await context.send(embed=embed) + elif isinstance(error, commands.MissingPermissions): + embed = discord.Embed( + title="Error!", + description="You are missing the permission(s) `" + ", ".join( + error.missing_permissions) + "` to execute this command!", + color=0xE02B2B + ) + await context.send(embed=embed) + elif isinstance(error, commands.MissingRequiredArgument): + embed = discord.Embed( + title="Error!", + # We need to capitalize because the command arguments have no capital letter in the code. + description=str(error).capitalize(), + color=0xE02B2B + ) + await context.send(embed=embed) + raise error + +# --------------------------------------------------------------------------------------- +# Load COGS for this bot +# --------------------------------------------------------------------------------------- +async def load_cogs() -> None: + for file in os.listdir(f"./cogs"): + if file.endswith(".py"): + extension = file[:-3] + try: + await bot.load_extension(f"cogs.{extension}") + print(f"Loaded extension '{extension}'") + except Exception as e: + exception = f"{type(e).__name__}: {e}" + print(f"Failed to load extension {extension}\n{exception}") + + +# --------------------------------------------------------------------------------------- +# LEGACY BOT COMMANDS +# --------------------------------------------------------------------------------------- +# [perfix]groupDM {<@&role>} (message) +# --------------------------------------------------------------------------------------- +@bot.command() +@checks.is_owner() +async def groupDM(context, role: discord.Role, *, message: str) -> None: + for member in role.members: + await member.send(message) + +# --------------------------------------------------------------------------------------- +# LIST categories, text channels, voice channels, all channels + categories +# --------------------------------------------------------------------------------------- +# Categories +# --------------------------------------------------------------------------------------- +@bot.command() +@checks.is_owner() +async def cata(context) -> None: # async def cat(context): + for category in context.message.guild.categories: # for category in context.message.guild.categories: + await context.send(f"`{category.id}` - <#{category.id}>") # print(category.name) + +# --------------------------------------------------------------------------------------- +# Text channels +# --------------------------------------------------------------------------------------- +@bot.command() +@checks.is_owner() +async def txtChannel(context) -> None: + for text_channel in context.message.guild.text_channels: + await context.send(f"`{text_channel.id}` - <#{text_channel.id}>") + +# --------------------------------------------------------------------------------------- +# Voice channels +# --------------------------------------------------------------------------------------- +@bot.command() +@checks.is_owner() +async def vc(context) -> None: + for voice_channel in context.message.guild.voice_channels: + await context.send(f"`{voice_channel.id}` - <#{voice_channel.id}>") + +# --------------------------------------------------------------------------------------- +# All Channels + Categories +# --------------------------------------------------------------------------------------- +@bot.command() +@checks.is_owner() +async def channels(context) -> None: + for channel in context.message.guild.channels: + await context.send(f"`{channel.id}` - <#{channel.id}>") + +# --------------------------------------------------------------------------------------- +# BOT logs on_guild_join +# --------------------------------------------------------------------------------------- +@bot.event +async def on_guild_join(guild) -> None: + print(guild.name) + print(guild.id) + +# --------------------------------------------------------------------------------------- +# BOT logs on_guild_leave +# --------------------------------------------------------------------------------------- +@bot.event +async def on_guild_leave(guild) -> None: + print(guild.name) + print(guild.id) + +# --------------------------------------------------------------------------------------- +# verify into the server +# --------------------------------------------------------------------------------------- +@bot.command() +async def verifyme(context, member=None) -> None: + + verifyrole = discord.utils.get(context.guild.roles, name="ROLE_NAME") # YOU MUST SPECIFY THE ROLE NAME + try: + await context.author.add_roles(verifyrole) + except Exception as errortoaddroles: + await context.send(f'Error in add roles\n```py\n{errortoaddroles}```') + await context.message.delete() + await member.send(f'{context.author} | {context.author.id} Should now be verified.') + +# --------------------------------------------------------------------------------------- +# Countdown/timer +# --------------------------------------------------------------------------------------- +@bot.command() +async def countdown(ctx, t: int): + await ctx.send(f"Counting down from {t}s") + + while t > 0: + t -= 1 + # Sleep for 1 second + await asyncio.sleep(1) + + await ctx.send("Countdown end reached") + +# --------------------------------------------------------------------------------------- +# Creating a role, only specifying it's name +# --------------------------------------------------------------------------------------- +@bot.command() +async def create_role(ctx, *, name): + # Create the role + await ctx.guild.create_role(name=name) + await ctx.send(f"Created role with name {name}") + +# --------------------------------------------------------------------------------------- +# Adding a role to everybody in the server +# --------------------------------------------------------------------------------------- +@bot.command() +async def addroles(ctx, *, role: discord.Role): + for m in ctx.guild.members: + await m.add_roles(role) + await asyncio.sleep(1) + await ctx.send("Added roles") + +# --------------------------------------------------------------------------------------- +# You can also specify specific users to add a role to: +# --------------------------------------------------------------------------------------- +@bot.command() +async def addrole(ctx, role: discord.Role, *members: discord.Member): + for m in members: + await m.add_roles(role) + print(f":white_check_mark: Role {role} added to {m.mention}") + +# --------------------------------------------------------------------------------------- +# Sending messages *, arg is useful if you want to consume all as a string +# --------------------------------------------------------------------------------------- +@bot.command() +async def echo(ctx, *, message): + # Delete message that invokes command + await ctx.delete() + + # Repeat back the input and auto delete after 20 seconds + await ctx.send(message, delete_after=20) + + +# --------------------------------------------------------------------------------------- +# Sending messages You can loop over each text channel in a guild, and send a message +# --------------------------------------------------------------------------------------- +@bot.command() +async def sendToAll(ctx, *, message): + for channel in ctx.guild.text_channels: + await channel.send(message) + +# --------------------------------------------------------------------------------------- +# User info +# --------------------------------------------------------------------------------------- +def getstatus(m): + if str(m.status) == "dnd": + return "do not disturb" + return m.status + +@bot.command() +async def userinfo(ctx, member: discord.Member): + c_delta = datetime.utcnow() + c_at = member.created_at.strftime("%c") + join_pos = sorted(ctx.guild.members, key=lambda member: member.joined_at).index(member) + 1 + + embed = discord.Embed( + title=f"{member.name}#{member.discriminator}", + timestamp=datetime.utcnow(), + colour=0x000 + ) + embed.add_field( + name="Status:", + value=getstatus(member), + inline=True + ) + embed.add_field( + name="Guild name:", + value=member.display_name, + inline=True + ) + embed.add_field( + name="Join position:", + value=f"{join_pos}/{len(ctx.guild.members)}", + inline=True + ) + embed.add_field( + name="Created at:", + value=f"{c_at}", + inline=True + ) + embed.add_field( + name="ID:", + value=member.id, + inline=True + ) + embed.add_field( + name="Bot:", + value="✅ Yes" if member.bot else "❌ No", + inline=True + ) + # Setting the thumbnail as the users profile picture + embed.set_thumbnail( + url=member.avatar + ) + # Setting a footer + embed.set_footer( + text=f"Requested by {ctx.author.name}", + icon_url=ctx.author.avatar + ) + await ctx.send(embed=embed) + +# --------------------------------------------------------------------------------------- +# Background task and command group +# We can make command groups which will allow us make subcommands +# --------------------------------------------------------------------------------------- +@tasks.loop(seconds=60) # Loop every 60 seconds +async def loop_function(ctx, message): + await ctx.send(message) + +@bot.group(invoke_without_command=True) # Specify that we don't want the 'first' command called when we use a subcommand +async def loop(ctx): + await ctx.send("**1.** Start loop: `.loop start {message}`\n**2.** Stop loop: `.loop stop`") + +@loop.command() # Subcommand named 'start', so called like this: "PREFIXloop start message goes here" +async def start(ctx, *, message): + await ctx.send("**Starting loop...**") + # Starting the loop function + loop_function.start(ctx, message) + +@loop.command() # Subcommand named stop +async def stop(ctx): + await ctx.send("**Stopping loop...**") + # Stopping the loop + loop_function.stop() + +# --------------------------------------------------------------------------------------- +# Getting online members/offline members +# --------------------------------------------------------------------------------------- +@bot.command() +async def online(ctx): + online_m, offline_m = [], [] + # Loop over each member in guild.members + for m in ctx.guild.members: + # Add to list of online members (online_m) if status is online/dnd, else add to offline_m + (online_m if str(m.status) in ("online", "dnd") else offline_m).append(str(m)) + await ctx.send(f"Online: {', '.join(online_m)}\nOffline: {', '.join(offline_m)}") + +# --------------------------------------------------------------------------------------- +# Fetching audit log +# --------------------------------------------------------------------------------------- +@bot.command() +async def logs(ctx): + actions = [] + async for entry in ctx.guild.audit_logs(limit = 10): + time = entry.created_at.strftime("%d-%m-%Y %H:%M:%S") + actions.append(f"`{entry.user}` did `{entry.action}` at `{time}` to `{entry.target}`\n\n") + + embed = discord.Embed( + title="Audit log", + description=''.join(actions), + colour=0x000 + ) + await ctx.send(embed=embed) + +# --------------------------------------------------------------------------------------- +# Evaluates customized code +# --------------------------------------------------------------------------------------- +@bot.command(aliases=['e', 'evaluate']) +@commands.is_owner() +async def eval(context, *, code): + """Evaluates customized code""" + language_specifiers = ["python", "py", "javascript", "js", "html", "css", "php", "md", "markdown", "go", "golang", "c", "c++", "cpp", "c#", "cs", "csharp", "java", "ruby", "rb", "coffee-script", "coffeescript", "coffee", "bash", "shell", "sh", "json", "http", "pascal", "perl", "rust", "sql", "swift", "vim", "xml", "yaml"] + loops = 0 + while code.startswith("`"): + code = "".join(list(code)[1:]) + loops += 1 + if loops == 3: + loops = 0 + break + for language_specifier in language_specifiers: + if code.startswith(language_specifier): + code = code.lstrip(language_specifier) + try: + while code.endswith("`"): + code = "".join(list(code)[0:-1]) + loops += 1 + if loops == 3: + break + code = "\n".join(f" {i}" for i in code.splitlines()) + code = f"async def eval_expr():\n{code}" + def send(text): + bot.loop.create_task(context.send(text)) + env = { + "bot": bot, + "client": bot, + "context": context, + "print": send, + "_author": context.author, + "_message": context.message, + "_channel": context.channel, + "_guild": context.guild, + "_me": context.me + } + env.update(globals()) + exec(code, env) + eval_expr = env["eval_expr"] + result = await eval_expr() + await context.message.add_reaction("\N{WHITE HEAVY CHECK MARK}") + if result: + await context.send(result) + except Exception as learntofuckingcode: + await context.message.add_reaction("\N{WARNING SIGN}") + await context.send(f'**Error**```py\n{learntofuckingcode}```') + +# --------------------------------------------------------------------------------------- +# RUN BOT +# --------------------------------------------------------------------------------------- +asyncio.run(init_db()) +asyncio.run(load_cogs()) +bot.run(config["token"]) \ No newline at end of file diff --git a/readme.md b/readme.md index 9796701..bd4ef1d 100644 --- a/readme.md +++ b/readme.md @@ -18,7 +18,7 @@ $ cd ./Discord-Bot-Python ## Create .env enviroment - NOTE: If you going to use .env environment you will need to make the necessary changes to the bots code inorder for it to work -- Using .env files can be [found here](https://dev.to/jakewitcher/using-env-files-for-environment-variables-in-python-applications-55a1 +- Using .env files can be found [here](https://dev.to/jakewitcher/using-env-files-for-environment-variables-in-python-applications-55a1 ) - Create [.env](.env) file in the root director. - Here is an explanation of what the values should look like: [env_demo.env](env_demo.env) @@ -32,6 +32,7 @@ TOMORROWIO_TOKEN=WEATHER_API_TOKEN ## Create config.json + - Create [config.json](config.json) file in the root directory. - Here is an explanation of what the values should look like: [demo.config.json](demo.config.json) - Example: @@ -73,17 +74,17 @@ $ pip install -r requirements.txt - If you have just installed python today, then you just need to use the following command: ```shell -$ python bot.py +$ python main.py ``` - If you have multiple versions of python installed (2.x and 3.x) then you will need to use the following command: ```shell -$ python3 bot.py +$ python3 main.py ``` - or eventually ```shell -$ python3.x bot.py +$ python3.x main.py ``` - Replace `x` with the version of Python you have installed.