diff options
author | Parker <contact@pkrm.dev> | 2025-01-25 22:58:41 -0600 |
---|---|---|
committer | Parker <contact@pkrm.dev> | 2025-01-25 22:58:41 -0600 |
commit | 3c18d552c2c9eb15834b33687915a7b16c98883b (patch) | |
tree | cf502397da128e3c31dc8f45397d1824dd7c2df9 |
reupload
117 files changed, 3714 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6851893 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +config.yml +config.ini +.DS_STORE +__pycache__ +count.db +.vscode +winning_reels +losing_reels_1 +losing_reels_2 +losing_reels_3 +losing_reels_4 +losing_reels_5
\ No newline at end of file diff --git a/AquaBot.png b/AquaBot.png Binary files differnew file mode 100644 index 0000000..6467228 --- /dev/null +++ b/AquaBot.png @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <https://unlicense.org> diff --git a/README.md b/README.md new file mode 100644 index 0000000..31e5a2a --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +<h1 align="center"> + <br> + <img src="AquaBot.png" width="250" alt="Aqua Bot Image"></a> + <br> + Aqua Bot<br> +</h1> + +<h3 align="center"> + Multipurpse Discord bot made on d.py +</h3> + +<p align="center"> + <a href="https://github.com/Rapptz/discord.py/"> + <img src="https://img.shields.io/badge/discord-py-blue.svg" alt="discord.py"> + </a> +</p> + +# Aqua Bot + +Aqua Bot is a great multipurpose Discord bot that is open source and offers all features for free. A list of the features for Aqua Bot can be found below. + +Aqua Bot is no longer under development and does not run anymore. Attention has been switched to the music only Discord bot, Guava, which can be found [here](https://github.com/packetparker/guava) + +### Features +- Music - Play music from YouTube, Spotify, SoundCloud, Deezer, and Bandcamp +- Moderation - Kick, ban, mute, tempmute, warn, etc. +- Economy - Global leaderboard to show who has the most money, and buy ranks to show off to others +- Gambling - Gamble your money at the blackjack table or on the slot machines, or flip a coin +- Random - Get crypto price data, information on a users Discord account, etc. + +<br> + +## Selfhost + +If you want to selfhost your own version of Aqua Bot, follow the instructions below. + +Downlooad the code and install the necessary dependencies using pip (ex: `pip install -r requirements.txt`) + +On first run, you will likely get a critical warning in your console, don't worry, this is excepted. It will automatically create a `config.ini` file for you in the root of the directory with all of the necessary configuration options. + +Fill out all of the configuration options, ALL options must be filled out. For help on what each option does, look at the table below. + +### BOT_INFO Configuration + +Field | Description +--- | --- +TOKEN | The token for your bot +BONUS_COOLDOWN | Cooldown time, in hours, between uses of the `/add` command, which gives users $10,000 +BOT_COLOR | Hex color code for the color used on most of the message embeds +BUG_CHANNEL_ID | Channel ID for the bug reporting channel +FEEDBACK_CHANNEL_ID | Channel ID for the feedback message channel + +### CRYPTO_COMPARE Configuration +Field | Description +--- | --- +API_KEY | API key from your CryptoCompare account. Can be aquired [here](https://min-api.cryptocompare.com/). + +### POSTGESQL Configuration +Field | Description +--- | --- +USERNAME | Username for login +PASSWORD | Password for login +HOST | Host for connecting to database +PORT | Port for connecting to database +DATABASE | Name of the database to be used + +### LAVALINK Configuration +Field | Description +--- | --- +HOST | Host for connecting to Lavalink +PORT | Port for connecting to Lavalink +PASSWORD | Password for login + +### SPOTIFY CONFIGURATION +Field | Description +--- | --- +CLIENT_ID | Client ID given to you from Spotify Developer Dashboard +CLIENT_SECRET | Client Secret, found in the same place + +Once all options are properly configured, you must also setup a Lavalink server in order for the music features to work. For help on setting up a Lavalnk server, follow the docs [here](https://lavalink.dev/getting-started/). + +Once your Lavalink server has been configured, you can now, finally, start the bot again by running the `bot.py` file and everything should work.
\ No newline at end of file diff --git a/code/bot.py b/code/bot.py new file mode 100644 index 0000000..27b17f0 --- /dev/null +++ b/code/bot.py @@ -0,0 +1,75 @@ +import discord +from discord.ext import commands, tasks +import os +import zipfile +import tqdm +import requests + +from validate_config import create_config +from database import init_database +from global_variables import LOG, BOT_TOKEN, SPOTIFY_CLIENT_ID, SPOTIFY_CLIENT_SECRET + + +def unpack_reels(): + # Files ending in .zip within /code/utils, extract it to /code/utils + for file in os.listdir("code/utils"): + # If folders exist, don't unpack + if (os.path.exists("code/utils/winning_reels") and + os.path.exists("code/utils/losing_reels_1") and + os.path.exists("code/utils/losing_reels_2") and + os.path.exists("code/utils/losing_reels_3") and + os.path.exists("code/utils/losing_reels_4") and + os.path.exists("code/utils/losing_reels_5")): + break + if file.endswith(".zip"): + with zipfile.ZipFile(f"code/utils/{file}", 'r') as zip_ref: + for member in tqdm.tqdm(iterable=zip_ref.namelist(), total=len(zip_ref.namelist()), desc=f"Unpacking {file}..."): + zip_ref.extract(member, "code/utils") + + +class MyBot(commands.Bot): + def __init__(self): + super().__init__( + command_prefix='***', + activity = discord.Game(name="Ping Me For Help!"), + intents = discord.Intents.default() + ) + async def setup_hook(self): + unpack_reels() + create_config() + get_access_token.start() + await init_database() + for ext in os.listdir('./code/cogs'): + if ext.endswith('.py'): + await self.load_extension(f'cogs.{ext[:-3]}') + + +bot = MyBot() +bot.count_hold = 0 +bot.remove_command('help') + + +@bot.event +async def on_ready(): + LOG.info(f"{bot.user} has connected to Discord.") + + +@tasks.loop(minutes=45) +async def get_access_token(): + auth_url = 'https://accounts.spotify.com/api/token' + data = { + 'grant_type': 'client_credentials', + 'client_id': SPOTIFY_CLIENT_ID, + 'client_secret': SPOTIFY_CLIENT_SECRET + } + response = requests.post(auth_url, data=data) + access_token = response.json()['access_token'] + bot.access_token = access_token + +class InsufficientFundsException(Exception): + def __init__(self) -> None: + super().__init__() + + +if __name__ == '__main__': + bot.run(BOT_TOKEN)
\ No newline at end of file diff --git a/code/cogs/blackjack.py b/code/cogs/blackjack.py new file mode 100644 index 0000000..3912993 --- /dev/null +++ b/code/cogs/blackjack.py @@ -0,0 +1,257 @@ +import asyncio +import os +import random +from typing import List, Tuple +import discord +from discord.ext import commands +from PIL import Image +from discord import app_commands + +from bot import InsufficientFundsException +from database import Database + + +"""NOTE: This code was found somewhere on GitHub a long time ago. I changed it a bit to work for +discord.py 2.0 and for my needs. If anyone knows who wrote this, please let me know so I can +give them credit.""" + +Entry = Tuple[int, int] + +class Card: + suits = ["clubs", "diamonds", "hearts", "spades"] + def __init__(self, suit: str, value: int, down=False): + self.suit = suit + self.value = value + self.down = down + self.symbol = self.name[0].upper() + + @property + def name(self) -> str: + """The name of the card value.""" + if self.value <= 10: return str(self.value) + else: return { + 11: 'jack', + 12: 'queen', + 13: 'king', + 14: 'ace', + }[self.value] + + @property + def image(self): + return ( + f"{self.symbol if self.name != '10' else '10'}"\ + f"{self.suit[0].upper()}.png" \ + if not self.down else "red_back.png" + ) + + def flip(self): + self.down = not self.down + return self + + def __str__(self) -> str: + return f'{self.name.title()} of {self.suit.title()}' + + def __repr__(self) -> str: + return str(self) + + +class Blackjack(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.economy = Database(bot) + + async def check_bet( + self, + interaction: discord.Interaction, + bet + ): + bet = int(bet) + if bet <= 0: + raise commands.errors.BadArgument() + current = (await self.economy.get_entry(interaction.user.id))[1] + if bet > current: + raise InsufficientFundsException() + + @staticmethod + def hand_to_images(hand: List[Card]) -> List[Image.Image]: + return ([ + Image.open(f'./code/utils/cards/{card.image}') + for card in hand + ]) + + @staticmethod + def center(*hands: Tuple[Image.Image]) -> Image.Image: + """Creates blackjack table with cards placed""" + bg: Image.Image = Image.open('./code/utils/table.png') + bg_center_x = bg.size[0] // 2 + bg_center_y = bg.size[1] // 2 + + img_w = hands[0][0].size[0] + img_h = hands[0][0].size[1] + + start_y = bg_center_y - (((len(hands)*img_h) + \ + ((len(hands) - 1) * 15)) // 2) + for hand in hands: + start_x = bg_center_x - (((len(hand)*img_w) + \ + ((len(hand) - 1) * 10)) // 2) + for card in hand: + bg.alpha_composite(card, (start_x, start_y)) + start_x += img_w + 10 + start_y += img_h + 15 + return bg + + def output(self, name, *hands: Tuple[List[Card]]) -> None: + self.center(*map(self.hand_to_images, hands)).save(f'./code/players/tables/{name}.png') + + @staticmethod + def calc_hand(hand: List[List[Card]]) -> int: + """Calculates the sum of the card values and accounts for aces""" + non_aces = [c for c in hand if c.symbol != 'A'] + aces = [c for c in hand if c.symbol == 'A'] + sum = 0 + for card in non_aces: + if not card.down: + if card.symbol in 'JQK': sum += 10 + else: sum += card.value + for card in aces: + if not card.down: + if sum <= 10: sum += 11 + else: sum += 1 + return sum + + + @app_commands.command() + @app_commands.describe(bet='Amount of money to bet') + async def blackjack( + self, + interaction: discord.Interaction, + bet: app_commands.Range[int, 1, None] + ): + "Bet your money on a blackjack game vs. the dealer" + + if f"{interaction.user.id}.png" in os.listdir("./code/players/tables"): + await interaction.response.send_message(f"{interaction.user.mention}, It appears you have a game already running in this server or another, please finish that game before starting a new one.", ephemeral=True) + + else: + await self.check_bet(interaction, bet) + deck = [Card(suit, num) for num in range(2,15) for suit in Card.suits] + random.shuffle(deck) # Generate deck and shuffle it + + player_hand: List[Card] = [] + dealer_hand: List[Card] = [] + + player_hand.append(deck.pop()) + dealer_hand.append(deck.pop()) + player_hand.append(deck.pop()) + dealer_hand.append(deck.pop().flip()) + + player_score = self.calc_hand(player_hand) + dealer_score = self.calc_hand(dealer_hand) + + async def out_table(**kwargs) -> discord.Interaction: + self.output(f'{interaction.user.id}', dealer_hand, player_hand) + embed = discord.Embed(**kwargs) + file = discord.File( + f"./code/players/tables/{interaction.user.id}.png", filename=f"{interaction.user.id}.png" + ) + embed.set_image(url=f"attachment://{interaction.user.id}.png") + try: + msg = await interaction.response.send_message(file=file, embed=embed) + except: + msg = await interaction.edit_original_response(attachments=[file], embed=embed) + reac = await interaction.original_response() + await reac.clear_reactions() + return msg + + standing = False + + while True: + player_score = self.calc_hand(player_hand) + dealer_score = self.calc_hand(dealer_hand) + if player_score == 21: # win condition + await self.economy.add_money(interaction.user.id, bet*2) + result = (f"Blackjack! - you win ${bet*2:,}", 'won') + break + elif player_score > 21: # losing condition + await self.economy.add_money(interaction.user.id, bet*-1) + result = (f"Player busts - you lose ${bet:,}", 'lost') + break + msg = await out_table( + title="Your Turn", + description=f"Your hand: {player_score}\n" \ + f"Dealer's hand: {dealer_score}" + ) + + reac = await interaction.original_response() + await reac.add_reaction("🇭") + await reac.add_reaction("🇸") + + buttons = {"🇭", "🇸"} + + try: + reaction, _ = await self.bot.wait_for( + 'reaction_add', check=lambda reaction, user:user == interaction.user and reaction.emoji in buttons, timeout=60 + ) + except asyncio.TimeoutError: + os.remove(f'./code/players/tables/{interaction.user.id}.png') + await interaction.followup.send(f"{interaction.user.mention} your game timed out. No money was lost or gained.") + return + + if str(reaction.emoji) == "🇭": + player_hand.append(deck.pop()) + continue + elif str(reaction.emoji) == "🇸": + standing = True + break + + if standing: + dealer_hand[1].flip() + player_score = self.calc_hand(player_hand) + dealer_score = self.calc_hand(dealer_hand) + + while dealer_score < 17: # dealer draws until 17 or greater + dealer_hand.append(deck.pop()) + dealer_score = self.calc_hand(dealer_hand) + + if dealer_score == 21: # winning/losing conditions + await self.economy.add_money(interaction.user.id, bet*-1) + result = (f"Dealer blackjack - you lose ${bet:,}", 'lost') + elif dealer_score > 21: + await self.economy.add_money(interaction.user.id, bet*2) + result = (f"Dealer busts - you win ${bet*2:,}", 'won') + elif dealer_score == player_score: + result = (f"Tie - you keep your money", 'kept') + elif dealer_score > player_score: + await self.economy.add_money(interaction.user.id, bet*-1) + result = (f"You lose ${bet:,}", 'lost') + elif dealer_score < player_score: + await self.economy.add_money(interaction.user.id, bet*2) + result = (f"You win ${bet*2:,}", 'won') + + color = ( + discord.Color.red() if result[1] == 'lost' + else discord.Color.green() if result[1] == 'won' + else discord.Color.blue() + ) + + if result[1] == 'won': + description=( + f"**You won ${bet*2:,}**\nYour hand: {player_score}\n" + + f"Dealer's hand: {dealer_score}" + ) + + elif result[1] == 'lost': + description=( + f"**You lost ${bet:,}**\nYour hand: {player_score}\n" + + f"Dealer's hand: {dealer_score}" + ) + + msg = await out_table( + title=result[0], + color=color, + ) + os.remove(f'./code/players/tables/{interaction.user.id}.png') + + +async def setup(bot: commands.Bot): + await bot.add_cog(Blackjack(bot))
\ No newline at end of file diff --git a/code/cogs/coinflip.py b/code/cogs/coinflip.py new file mode 100644 index 0000000..9771cbd --- /dev/null +++ b/code/cogs/coinflip.py @@ -0,0 +1,62 @@ +import discord +from discord.ext import commands +from discord import app_commands +import random + +from bot import InsufficientFundsException +from database import Database + + +class CoinFlip(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.economy = Database(bot) + + async def check_bet( + self, + interaction: discord.Interaction, + bet + ): + bet = int(bet) + if bet <= 0: + raise commands.errors.BadArgument() + current = (await self.economy.get_entry(interaction.user.id))[1] + if bet > current: + raise InsufficientFundsException() + + + @app_commands.command() + @app_commands.checks.cooldown(1, 2) + @app_commands.describe(bet="The amount of money you want to bet") + async def coinflip( + self, + interaction: discord.Interaction, + bet: int + ): + "Bet money on a coinflip (heads=win, tails=lose)" + + await self.check_bet(interaction, bet) + + if random.randint(0, 2) == 0: + await self.economy.add_money(interaction.user.id, bet*2) + embed = discord.Embed( + title=f"You won ${bet*2:,}!", + description="You flipped heads!", + color=discord.Color.green() + ) + + return await interaction.response.send_message(embed=embed) + + else: + await self.economy.add_money(interaction.user.id, bet*-1) + embed = discord.Embed( + title=f"You lost ${bet:,}!", + description="You flipped tails!", + color=discord.Color.red() + ) + + return await interaction.response.send_message(embed=embed) + + +async def setup(bot: commands.Bot): + await bot.add_cog(CoinFlip(bot))
\ No newline at end of file diff --git a/code/cogs/count.py b/code/cogs/count.py new file mode 100644 index 0000000..67e0139 --- /dev/null +++ b/code/cogs/count.py @@ -0,0 +1,40 @@ +from discord.ext import commands, tasks +import aiosqlite +import sqlite3 + +class Count(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def cog_load(self): + self.dump_count.start() + + + @tasks.loop(seconds=5) + async def dump_count(self): + try: + cur = await aiosqlite.connect("./code/count/count.db") + count = await cur.execute("SELECT count FROM count") + count = await count.fetchone() + if count is None: + await cur.execute("INSERT INTO count (count) VALUES (?)", (self.bot.count_hold,)) + else: + await cur.execute("UPDATE count SET count = count + ?", (self.bot.count_hold,)) + await cur.commit() + await cur.close() + self.bot.count_hold = 0 + except sqlite3.OperationalError: + try: + await cur.commit() + await cur.close() + except: + pass + + + @commands.Cog.listener() + async def on_app_command_completion(self, interaction, command): + self.bot.count_hold += 1 + + +async def setup(bot): + await bot.add_cog(Count(bot))
\ No newline at end of file diff --git a/code/cogs/covid.py b/code/cogs/covid.py new file mode 100644 index 0000000..bc26432 --- /dev/null +++ b/code/cogs/covid.py @@ -0,0 +1,68 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +import requests + +covid_dict = {'total_cases':0, 'total_deaths':0, 'total_recovered':0, 'active_cases':0, + 'critical_cases':0, 'cases_today':0, 'deaths_today':0, 'tests':0} + +class Covid(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def cog_load(self): + self.update_covid.start() + + @tasks.loop(seconds=300) + async def update_covid(self): + response = requests.get("https://disease.sh/v3/covid-19/all") + + if response.status_code == 200: + data = response.json() + cases = int(data["cases"]) + deaths = int(data["deaths"]) + recovered = int(data["recovered"]) + active = int(data["active"]) + critical = int(data["critical"]) + cases_today = int(data["todayCases"]) + deaths_today = int(data["todayDeaths"]) + tests = int(data["tests"]) + + else: + return + + update_crypto_dict = {'total_cases':cases, 'total_deaths':deaths, + 'total_recovered':recovered, 'active_cases':active, 'critical_cases':critical, + 'cases_today':cases_today, 'deaths_today':deaths_today, 'tests':tests} + + covid_dict.update(update_crypto_dict) + + + @app_commands.command() + async def covid( + self, + interaction: discord.Interaction + ): + "Get current global Covid-19 data" + embed = discord.Embed( + title = "World COVID-19 Data", + description = "Data is updated once every 5 minutes" + ) + embed.add_field(name="Total Cases", value=f"```{covid_dict['total_cases']:,}```", inline=True) + embed.add_field(name="Total Deaths", value=f"```{covid_dict['total_deaths']:,}```", inline=True) + embed.add_field(name="Total Recovered", value=f"```{covid_dict['total_recovered']:,}```", inline=True) + embed.add_field(name="Active Cases", value=f"```{covid_dict['active_cases']:,}```", inline=True) + embed.add_field(name="Critical Cases", value=f"```{covid_dict['critical_cases']:,}```", inline=True) + embed.add_field(name="Cases Today", value=f"```{covid_dict['cases_today']:,}```", inline=True) + embed.add_field(name="Deaths Today", value=f"```{covid_dict['deaths_today']:,}```", inline=True) + embed.add_field(name="Tests", value=f"```{covid_dict['tests']:,}```", inline=True) + + embed.set_footer(text="Information provided from: https://disease.sh/v3/covid-19/all") + file = discord.File("./code/utils/covid.png", filename="covid.png") + embed.set_thumbnail(url="attachment://covid.png") + + await interaction.response.send_message(embed=embed, file=file) + + +async def setup(bot): + await bot.add_cog(Covid(bot))
\ No newline at end of file diff --git a/code/cogs/crypto.py b/code/cogs/crypto.py new file mode 100644 index 0000000..9ef8984 --- /dev/null +++ b/code/cogs/crypto.py @@ -0,0 +1,126 @@ +import discord +from discord.ext import commands, tasks +from discord import app_commands +from typing import Literal +from decimal import Decimal +import requests + +from global_variables import CRYPTO_COMPARE_API_KEY + + +master_dict = {'Bitcoin (BTC)':'0:0:0:0:0:0', 'Ethereum (ETH)':'0:0:0:0:0:0', + 'Binance Coin (BNB)':'0:0:0:0:0:0', 'Solana (SOL)':'0:0:0:0:0:0', 'Cardano (ADA)':'0:0:0:0:0:0', + 'XRP (XRP)':'0:0:0:0:0:0', 'Polkadot (DOT)':'0:0:0:0:0:0','Dogecoin (DOGE)':'0:0:0:0:0:0', + 'Avalanche (AVAX)':'0:0:0:0:0:0', 'SHIBA INU (SHIB)':'0:0:0:0:0:0', 'Terra (LUNA)':'0:0:0:0:0:0', + 'Litecoin (LTC)':'0:0:0:0:0:0', 'Uniswap (UNI)':'0:0:0:0:0:0', 'Chainlink (LINK)':'0:0:0:0:0:0', + 'Polygon (MATIC)':'0:0:0:0:0:0', 'Algorand (ALGO)':'0:0:0:0:0:0', 'Bitcoin Cash (BCH)':'0:0:0:0:0:0', + 'VeChain (VET)':'0:0:0:0:0:0', 'Stellar (XLM)':'0:0:0:0:0:0', 'Internet Computer (ICP)':'0:0:0:0:0:0'} + +currencies = ['Bitcoin (BTC)', 'Ethereum (ETH)', 'Binance Coin (BNB)', 'Solana (SOL)', + 'Cardano (ADA)', 'XRP (XRP)', 'Polkadot (DOT)', 'Dogecoin (DOGE)', 'Avalanche (AVAX)', + 'SHIBA INU (SHIB)', 'Terra (LUNA)', 'Litecoin (LTC)', 'Uniswap (UNI)', 'Chainlink (LINK)', + 'Polygon (MATIC)', 'Algorand (ALGO)', 'Bitcoin Cash (BCH)', 'VeChain (VET)', 'Stellar (XLM)', + 'Internet Computer (ICP)'] + +connect_color_dict = {'Bitcoin (BTC)':0xf7931a, 'Ethereum (ETH)':0x627eea, + 'Binance Coin (BNB)':0xf3ba2f, 'Solana (SOL)':0x27dcb8, 'Cardano (ADA)':0x3cc8c8, + 'XRP (XRP)':0x00aae4, 'Polkadot (DOT)':0xf0047f,'Dogecoin (DOGE)':0xc3a634, + 'Avalanche (AVAX)':0xe84142, 'SHIBA INU (SHIB)':0xe93b24, 'Terra (LUNA)':0x5494f8, + 'Litecoin (LTC)':0x345d9d, 'Uniswap (UNI)':0xff027d, 'Chainlink (LINK)':0x335dd2, + 'Polygon (MATIC)':0x2bbdf7, 'Algorand (ALGO)':0x000000, 'Bitcoin Cash (BCH)':0x8dc351, + 'VeChain (VET)':0x15bdff, 'Stellar (XLM)':0x14b6e7, 'Internet Computer (ICP)':0xf15a24} + +connect_icon_dict = {'Bitcoin (BTC)':'bitcoin', 'Ethereum (ETH)':'ethereum', + 'Binance Coin (BNB)':'binance', 'Solana (SOL)':'solana', 'Cardano (ADA)':'cardano', + 'XRP (XRP)':'xrp', 'Polkadot (DOT)':'polkadot','Dogecoin (DOGE)':'dogecoin', + 'Avalanche (AVAX)':'avalanche', 'SHIBA INU (SHIB)':'shiba', 'Terra (LUNA)':'terra', + 'Litecoin (LTC)':'litecoin', 'Uniswap (UNI)':'uniswap', 'Chainlink (LINK)':'chainlink', + 'Polygon (MATIC)':'polygon', 'Algorand (ALGO)':'algorand', 'Bitcoin Cash (BCH)':'bitcoin_cash', + 'VeChain (VET)':'vechain', 'Stellar (XLM)':'stellar', 'Internet Computer (ICP)':'internet_computer'} + +class Crypto(commands.Cog): + def __init__(self, bot): + self.bot = bot + + async def cog_load(self): + self.update_crypto.start() + + def update_dicts_for_currency(self, response, cut_currency, currency): + round_num = 8 if cut_currency == 'SHIB' else 3 if cut_currency == 'DOGE' else 3 if cut_currency == 'VET' else 3 if cut_currency == 'XLM' else 2 + market = response.json()['RAW'][cut_currency]['USD']['LASTMARKET'] + price = round(Decimal(response.json()['RAW'][cut_currency]['USD']['PRICE']), round_num) + change_24h = round(Decimal(response.json()['RAW'][cut_currency]['USD']['CHANGEPCT24HOUR']), 2) + high = round(Decimal(response.json()['RAW'][cut_currency]['USD']['HIGH24HOUR']), round_num) + low = round(Decimal(response.json()['RAW'][cut_currency]['USD']['LOW24HOUR']), round_num) + mktcap = round(Decimal(response.json()['RAW'][cut_currency]['USD']['MKTCAP']), 2) + + master_dict[currency] = f'{market}:{price}:{change_24h}:{high}:{low}:{mktcap}' + + @tasks.loop(seconds=60) + async def update_crypto(self): + response = requests.get( + f'https://min-api.cryptocompare.com/data/pricemultifull?fsyms=BTC,ETH,BNB,SOL,ADA,XRP,DOT,DOGE,AVAX,SHIB,LUNA,LTC,UNI,LINK,MATIC,ALGO,BCH,VET,XLM,ICP&tsyms=USD&api_key={CRYPTO_COMPARE_API_KEY}' + ) + for currency in currencies: + cut_currency = currency[currency.find("(")+1:currency.find(")")] + self.update_dicts_for_currency(response, cut_currency, currency) + + + @app_commands.command() + async def prices( + self, + interaction: discord.Interaction, + ): + "See the current crypto prices" + embed = discord.Embed( + title="Current Crpytocurrency Price", + description=f"**In order to see more information on a specific cryptocurrency, do `/crypto <ticker>` to show price, percentage change, and more.** \n\nPrices are updated every 60 seconds. \nLetters following the name within () are known as the ticker. \nExample: Bitcoin (BTC) - The ticker is BTC", + color=discord.Color.gold() + ) + for key in master_dict: + price = master_dict[key].split(':')[1] + embed.add_field(name = f"{key}", value = f"```${Decimal(price):,}```", inline=True) + + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + @app_commands.describe(currency="Pick what cryptocurrency you want to see more information on.") + async def crypto( + self, + interaction: discord.Interaction, + currency: Literal['Bitcoin (BTC)', 'Ethereum (ETH)', 'Binance Coin (BNB)', 'Solana (SOL)', + 'Cardano (ADA)', 'XRP (XRP)', 'Polkadot (DOT)', 'Dogecoin (DOGE)', 'Avalanche (AVAX)', + 'SHIBA INU (SHIB)', 'Terra (LUNA)', 'Litecoin (LTC)', 'Uniswap (UNI)', 'Chainlink (LINK)', + 'Polygon (MATIC)', 'Algorand (ALGO)', 'Bitcoin Cash (BCH)', 'VeChain (VET)', 'Stellar (XLM)', + 'Internet Computer (ICP)'] + ): + "Send more information on a certain cryptocurrency" + icon_name = connect_icon_dict.get(currency) + color = connect_color_dict.get(currency) + market = master_dict[currency].split(':')[0] + price = master_dict[currency].split(':')[1] + price_change = master_dict[currency].split(':')[2] + high = master_dict[currency].split(':')[3] + low = master_dict[currency].split(':')[4] + mktcap = master_dict[currency].split(':')[5] + + embed = discord.Embed( + title=currency, + description="Information is updated every 60 seconds", + color=color + ) + embed.add_field(name="Market", value = f"```{market}```") + embed.add_field(name="Current Price", value=f"```${Decimal(price):,}```") + embed.add_field(name="24 Hour Change", value=f"```{Decimal(price_change):,}%```") + embed.add_field(name="24 Hour High", value=f"```${Decimal(high):,}```") + embed.add_field(name="24 Hour Low", value=f"```${Decimal(low):,}```") + embed.add_field(name="Market Cap", value=f"```${Decimal(mktcap):,}```") + file = discord.File(f"./code/utils/crypto_icons/{icon_name}.png", filename=f"{icon_name}.png") + embed.set_thumbnail(url=f"attachment://{icon_name}.png") + + await interaction.response.send_message(embed=embed, file=file) + + +async def setup(bot): + await bot.add_cog(Crypto(bot))
\ No newline at end of file diff --git a/code/cogs/gambling_helpers.py b/code/cogs/gambling_helpers.py new file mode 100644 index 0000000..5579df3 --- /dev/null +++ b/code/cogs/gambling_helpers.py @@ -0,0 +1,139 @@ +import discord +from discord.ext import commands +from discord import app_commands +import random + +from bot import InsufficientFundsException +from global_variables import BOT_COLOR, BONUS_COOLDOWN +from database import Database + + +class GamblingHelpers(commands.Cog): + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.economy = Database(bot) + + async def check_bet( + self, + interaction: discord.Interaction, + bet + ): + bet = int(bet) + if bet <= 0: + raise commands.errors.BadArgument() + current = (await self.economy.get_entry(interaction.user.id))[1] + if bet > current: + raise InsufficientFundsException() + + + # This is only here temporarily, because I accidentally deleted the database + @commands.command() + @commands.is_owner() + @commands.dm_only() + async def refund( + self, + ctx: commands.Context, + id, + amount: int + ): + user = await self.bot.fetch_user(id) + if user: + await self.economy.add_money(id, amount) + await ctx.send(f"Refunded {user} ${amount:,}") + else: + await ctx.send("User not found") + + + @app_commands.command() + @app_commands.checks.cooldown(1, BONUS_COOLDOWN*3600) + async def add( + self, + interaction: discord.Interaction + ): + f"Add $10,000 to your balance every {BONUS_COOLDOWN} hours" + amount = 10000 + await self.economy.add_money(interaction.user.id, amount) + embed = discord.Embed( + title="I've added $10,000 to you balance", + description=f"Come back again in {BONUS_COOLDOWN} hours.", + color=BOT_COLOR + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + + + @app_commands.command() + @app_commands.checks.cooldown(1, 120) + async def work( + self, + interaction: discord.Interaction + ): + "Work for a randomized amount of money every 2 minutes" + a = random.randint(500, 2500) + b = random.randint(500, 2500) + if a == b: + num = 50000000 + else: + num = a + b + + await self.economy.add_money(interaction.user.id, num) + embed = discord.Embed( + title=f"You worked and earned ${num:,}", + description="Come back again in 2 minutes.", + color=BOT_COLOR + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + + + @app_commands.command() + async def leaderboard( + self, + interaction: discord.Interaction + ): + "Show the global currency leaderboard" + entries = await self.economy.top_entries(5) + embed = discord.Embed( + title="Global Economy Leaderboard:", + description="", + color=discord.Color.gold() + ) + + for i, entry in enumerate(entries): + id = entry[0] + name = f"<@{id}>" + embed.description += f"**`{i+1}.` {name}**\n${entry[1]:,}\n\n" + + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + @app_commands.describe(user="The user to give money to") + @app_commands.describe(amount="The amount of money to give the user") + async def give( + self, + interaction: discord.Interaction, + user: discord.Member, + amount: app_commands.Range[int, 1, None] + ): + "Give money to another user" + if user == interaction.user: + embed = discord.Embed( + title="Self Error", + description="You cannot give money to yourself, please try again with a different user.", + color=BOT_COLOR + ) + return await interaction.response.send_message(embed=embed, ephemeral=True) + + else: + await self.check_bet(interaction, amount) + await self.economy.add_money(user.id, amount) + await self.economy.add_money(interaction.user.id, amount*-1) + embed = discord.Embed( + title="Gift Success", + description=f"You have successfully given {user.mention} ${amount:,}!", + color=discord.Color.green() + ) + return await interaction.response.send_message(embed=embed) + + +async def setup(bot: commands.Bot): + await bot.add_cog(GamblingHelpers(bot))
\ No newline at end of file diff --git a/code/cogs/handlers.py b/code/cogs/handlers.py new file mode 100644 index 0000000..d9c374f --- /dev/null +++ b/code/cogs/handlers.py @@ -0,0 +1,124 @@ +import discord +from discord.ext import commands +from discord.ext.commands.errors import * + +from bot import InsufficientFundsException +from global_variables import BOT_COLOR + + +class slash_handlers(commands.Cog): + def __init__(self, bot): + self.bot = bot + bot.tree.on_error = self.on_error + + async def on_error(self, interaction: discord.Interaction, error): + error = getattr(error, 'original', error) + + if isinstance(error, CommandNotFound): + return + + elif isinstance(error, ZeroDivisionError): + return + + elif isinstance(error, AttributeError): + return + + elif isinstance(error, InsufficientFundsException): + embed = discord.Embed( + title="Insufficient Funds!", + description=f"You do not have enough money to use that command. You can use `/add` to add more money. You can also check your current balance with `/profile`", + color=BOT_COLOR + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + elif isinstance(error, BadArgument): + embed = discord.Embed( + title="Bad Argument!", + description=f"The arguments you provided in the command are invalid. Please try again.", + color=BOT_COLOR + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + elif isinstance(error, discord.app_commands.errors.MissingPermissions): + embed = discord.Embed( + title="Missing Permissions!", + description=f"{error}", + color=BOT_COLOR + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + + elif isinstance(error, discord.app_commands.errors.BotMissingPermissions): + embed = discord.Embed( + title="Bot Missing Permissions!", + description=f"{error}", + color=BOT_COLOR + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + elif isinstance(error, discord.app_commands.errors.CommandOnCooldown) and interaction.command.name != "slots": + s = int(error.retry_after) + s = s % (24 * 3600) + h = s // 3600 + s %= 3600 + m = s // 60 + s %= 60 + + embed = discord.Embed( + title="Command On Cooldown!", + description=f"Please wait another {h}hrs {m}min {s}sec before trying that command again.", + color=BOT_COLOR + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + + elif isinstance(error, discord.app_commands.errors.CommandOnCooldown) and interaction.command.name == "slots": + embed = discord.Embed( + title="Slots Cooldown!", + description="To prevent spamming, the slots command in on a 4 second cooldown. Sorry for the inconvenience.", + color=BOT_COLOR + ) + await interaction.response.send_message(embed=embed, ephemeral=True) + + elif isinstance(error, UnboundLocalError): + await interaction.response.send_message(f"{interaction.user.mention}, your game timed out, no money was lost or gained.", ephemeral=True) + + elif isinstance(error, discord.errors.Forbidden) and interaction.command.name in ('kick', 'ban', 'softban'): + embed = discord.Embed( + title="Forbidden Error", + description=f"Moderation actions cannot be performed on the bot, or on members above the bot (like owners or administrators), please try again on users below me.", + color=BOT_COLOR + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + elif isinstance(error, discord.errors.Forbidden) and interaction.command.name in ('mute', 'tempmute', 'unmute'): + embed = discord.Embed( + title="Forbidden Error", + description=f"I cannot mute or unmute members with a role that is above mine. Please double check that my roles are listed above your servers muted role.", + color=BOT_COLOR + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + + elif isinstance(error, discord.errors.Forbidden) and interaction.command.name == 'purge': + embed = discord.Embed( + title="Permissions Error", + description=f"It appears im missing the `manage messages` permissions needed to be able to run the `purge` command..", + color=BOT_COLOR + ) + + await interaction.response.send_message(embed=embed, ephemeral=True) + + else: + raise error + + @commands.Cog.listener() + async def on_command_error(self, ctx, error): + return + + +async def setup(bot: commands.Bot): + await bot.add_cog(slash_handlers(bot)) diff --git a/code/cogs/help.py b/code/cogs/help.py new file mode 100644 index 0000000..4c6fd82 --- /dev/null +++ b/code/cogs/help.py @@ -0,0 +1,158 @@ +import discord +from discord.ext import commands +import re +from discord import app_commands + +from global_variables import BOT_COLOR + + +class HelpDropdown(discord.ui.Select): + def __init__(self): + + options = [ + discord.SelectOption(label='Economy', description='add, profile, shop, blackjack, slots, leaderboard', emoji="💰"), + discord.SelectOption(label='Moderation', description='mute, tempmute, unmute, kick, ban, softban, purge', emoji="⚔️"), + discord.SelectOption(label='Info', description='prices, crypto, covid, invite, userinfo, botinfo, vote, bug, feedback, ping', emoji="❓"), + discord.SelectOption(label='Music', description='play, skip, queue, remove, stop, clear, repeat, shuffle, nowplaying, pause, remove', emoji='🎵'), + discord.SelectOption(label='Admin', description='setmute, muterole, delmute', emoji="⚙️"), + ] + + super().__init__(placeholder='Choose a category...', min_values=1, max_values=1, options=options) + + async def callback(self, interaction: discord.Interaction): + if self.values[0] == 'Economy': + embed = discord.Embed( + title=":moneybag: - Economy Help", + description="**Options in `<>` are mandatory**", + color=BOT_COLOR + ) + embed.add_field(name="**Add**", value=f"**Usage: `/add`**\nGives you $10,000. Can be run every 2 hours", inline=False) + embed.add_field(name="**Work**", value=f"**Usage: `/Work`**\nGives you an amount of money between $1,000 and $5,000.", inline=False) + embed.add_field(name="**Shop**", value=f"**Usage: `/shop`**\nGives you the shop menus so that you can buy items", inline=False) + embed.add_field(name="**Blackjack**", value=f"**Usage: `/blackjack <bet>`**\nAllows you to play blackjack with the amount of money bet", inline=False) + embed.add_field(name="**Slots**", value=f"**Usage: `/slots <bet>`**\nTake your chances on the slots with a bet of your choice.", inline=False) + embed.add_field(name="**Profile**", value=f"**Usage: `/profile <member>`**\nShows the amount of money and ranks that a user has", inline=False), + embed.add_field(name="**Leaderboard**", value=f"**Usage: `/leaderboard` **\nShows the top 5 players with the most money. This is a global leaderboard and not per server.", inline=False) + await interaction.response.edit_message(embed=embed) + + if self.values[0] == 'Moderation': + embed = discord.Embed( + title=":crossed_swords: - Moderation Help", + description="**Options in `<>` are mandatory**", + color=BOT_COLOR + ) + embed.add_field(name="**Warn**", value=f"**Usage: `/warn <member> <reason>`** \nWarn a member for doing something against the rules.", inline=True) + embed.add_field(name="**Delwarn**", value=f"**Usage: `/delwarn <warn ID>`** \nDelete a warning from a member so that it is no longer on their record.", inline=True) + embed.add_field(name="**Warnings**", value=f"**Usage: `/warnings <member>`** \nSee all of the warnings for a member. Also includes when they were warned, and who warned them.", inline=True) + embed.add_field(name="**Mute**", value=f"**Usage: `/mute <member> <time>`** \nMute a member so they can't send anymore messages.", inline=True) + embed.add_field(name="**Tempmute**", value=f"**Usage: `/tempmute <member> <time - in hours>` \nExample: `/tempmute @bob 2`** \nMutes the member temporarily, they will be unmuted once the specified time has passed.", inline=True) + embed.add_field(name="**Unmute**", value=f"**Usage: `/unmute <member>`** \nUnmute a member so they are able to send messages again.", inline=True) + embed.add_field(name="**Purge**", value=f"**Usage: `/purge <amount>`** \nDelete messages from your server. Max amount that can be deleted at one time is `100` messages.") + embed.add_field(name="**Kick**", value=f"**Usage: `/kick <member> <reason>`** \nKick a member from your server. They will be able to join back with a new invite.", inline=True) + embed.add_field(name="**Ban**", value=f"**Usage: `/ban <member> <reason>`** \nBan a member from your server. They will not be able to join back until they are unbanned.", inline=True) + embed.add_field(name="**Softban**", value=f"**Usage: `/softban <member> <reason>`** \nThis command will ban and then immediately unban the member in order to get rid of their message history.", inline=True) + await interaction.response.edit_message(embed=embed) + + if self.values[0] == "Info": + embed = discord.Embed( + title=":question: - Info Help", + description="**Options in `<>` are mandatory**", + color=BOT_COLOR + ) + embed.add_field(name="**Prices**", value=f"**Usage: `/prices`** \nShows the prices for the 20 cryptocurrencies that we currently list", inline=True) + embed.add_field(name="**Crypto**", value=f"**Usage: `/crypto <ticker>`** \nShows expanded information on the specific currency given its ticker.", inline=True) + embed.add_field(name="**Covid**", value=f"**Usage: `/covid` **\nSends the current global COVID-19 data.", inline=True) + embed.add_field(name="**Invite**", value=f"**Usage: `/invite` **\nSends the invite for the bot.", inline=True) + embed.add_field(name="**User Info**", value=f"**Usage: `/userinfo <member>`** \nGives information on a member in your server. Information includes account creation date, when they joined your server, and some more.", inline=True) + embed.add_field(name="**Bot Info**", value=f"**Usage: `/botinfo`** \nGives information on the bot.", inline=True) + embed.add_field(name="**Vote**", value=f"**Usage: `/vote`** \nSends the link for you to vote for our bot on top.gg", inline=True) + embed.add_field(name="**Bug**", value=f"**Usage: `/bug`** \nShows a form to be filled out to notify the developer of a bug", inline=True) + embed.add_field(name="**Feedback**", value=f"**Usage: `/feedback`** \nShows a form to be filled out to show the developer feedback on the both", inline=True) + embed.add_field(name="**Ping**", value=f"**Usage: `/ping` **\nGives the current ping of the bot.", inline=True) + await interaction.response.edit_message(embed=embed) + + if self.values[0] == "Music": + embed = discord.Embed( + title=f":musical_note: - Music Help \n*NOTE - These commands are still in beta. Please report bugs using `/contact`", + description="**Options in `<>` are mandatory**", + color=BOT_COLOR + ) + embed.add_field(name="**Play**", value=f"**Usage: `/play <name/URL>` **\nSearches YouTube, and then plays the top song.", inline=True) + embed.add_field(name="**Skip**", value=f"**Usage: `/skip` **\nSkips the song that is currently playing.", inline=True) + embed.add_field(name="**Queue**", value=f"**Usage: `/queue`** \nSends all of the songs that are in the queue.", inline=True) + embed.add_field(name="**Remove**", value=f"**Usage: `/remove <song #>` **\nRemoves the specified song from the queue.", inline=True) + embed.add_field(name="**Stop**", value=f"**Usage: `/stop`** \nStops music, clears queue, and leaves VC.", inline=True), + embed.add_field(name="**Clear**", value=f"**Usage: `/clear` **\nRemoves ALL songs in the queue.", inline=True) + embed.add_field(name="**Repeat**", value=f"**Usage: `/remove`** \nRepeats the song that is playing. Run the command again to stop repeating.", inline=True) + embed.add_field(name="**Shuffle**", value=f"**Usage: `/shuffle`** \nWill play a random song in the queue. Run the command again to stop shuffling.", inline=True) + embed.add_field(name="**NowPlaying**", value=f"**Usage: `/np` **\nSends the song that is currently playing.", inline=True) + embed.add_field(name="**Pause**", value=f"**Usage: `/pause`** \nPauses the currently playing song.", inline=True) + embed.add_field(name="**Resume**", value=f"**Usage: `/resume` **\nResumes the paused song.", inline=True) + await interaction.response.edit_message(embed=embed) + + if self.values[0] == "Admin": + embed = discord.Embed( + title=":gear: - Admin Help", + description="**Options in `<>` are mandatory**", + color=BOT_COLOR + ) + embed.add_field(name="**Setmute**", value = f"**Usage: `/setmute <name of role>` **\nSets the role that will be given to users whenever you use the `/mute` command.", inline=True) + embed.add_field(name="**Delmute**", value = f"**Usage: `/delmute` **\nDeletes the muted role from our database.", inline=True) + embed.add_field(name="**Muterole**", value = f"**Usage: `/muterole`** \nSends the current role that is assigned as the muted role for your server.", inline=True) + await interaction.response.edit_message(embed=embed) + + else: + return + +class HelpView(discord.ui.View): + def __init__(self, timeout=180.0): + super().__init__(timeout=timeout) + self.value = None + self.add_item(HelpDropdown()) + url = "https://discord.com/api/oauth2/authorize?client_id=889027125275922462&permissions=8&scope=bot%20applications.commands" + self.add_item(discord.ui.Button(label="Invite Me", url=url, row=2)) + + @discord.ui.button(label='Main Page', style=discord.ButtonStyle.blurple, row=2) + async def main_page(self, interaction: discord.Interaction, button: discord.ui.Button): + embed = discord.Embed( + title="Help", + description=f"**IMPORTANT - A lot of stuff changed, please use the `new` command to see all of the changes** \n\nFor extended information on commands and categories, please choose an option from the dropdown menu below.", + color=BOT_COLOR + ) + await interaction.response.edit_message(embed=embed) + + +class Help(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @app_commands.command() + async def help( + self, + interaction: discord.Interaction + ): + "Sends the bots commands and features" + + embed = discord.Embed( + title="Help", + description=f"**IMPORTANT - All commands are now slash commands, and a few changes have been made. Please use `/new` to see any alterations.", + color=BOT_COLOR + ) + view = HelpView() + await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + + + @commands.Cog.listener() + async def on_message(self, message: discord.Message) -> None: + if re.fullmatch(rf"<@!?{self.bot.user.id}>", message.content): + embed = discord.Embed( + title=f"All commands are now slash commands!", + description=f"**Use `/help` in order to get help on what commands are available.**", + color=BOT_COLOR + ) + await message.reply(embed=embed) + + +async def setup(bot): + await bot.add_cog(Help(bot))
\ No newline at end of file diff --git a/code/cogs/info.py b/code/cogs/info.py new file mode 100644 index 0000000..f115cd3 --- /dev/null +++ b/code/cogs/info.py @@ -0,0 +1,77 @@ +import time +import discord +from discord.ext import commands +import datetime +from discord import app_commands + +from global_variables import BOT_COLOR + + +class Info(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @app_commands.command() + @app_commands.describe(member="Member whose information you want to view") + async def userinfo( + self, + interaction: discord.Interaction, + member: discord.Member + ): + "Send account information for the given user" + + embed = discord.Embed( + title=f"User Information For {member}", + color=BOT_COLOR + ) + + roles = [role for role in member.roles] + roles = f" ".join([f"{role.mention}, " for role in roles]) + + embed.set_thumbnail(url = member.avatar.url) + embed.add_field(name="Account name: ", value=f"`{str(member)}`") + embed.add_field(name="Discord ID: ", value=f"`{str(member.id)}`") + embed.add_field(name="Nickname: ", value=f"`{member.nick}`" or "`No nickname!`") + embed.add_field(name="Account created at: ", value=f"`{member.created_at.strftime('%Y-%m-%d')}`") + embed.add_field(name="Joined server at: ", value=f"`{member.joined_at.strftime('%Y-%m-%d')}`") + + if member.bot is True: + embed.add_field(name="Discord bot? ", value="`🤖 = ✅`") + else: + embed.add_field(name="Discord bot?", value="`🤖 = ❌`") + + embed.add_field(name="Top role: ", value=f"{member.top_role.mention}") + embed.add_field(name="Roles: ", inline=False, value=roles) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + + try: + await interaction.response.send_message(embed=embed) + + except discord.errors.HTTPException: + embed.remove_field(7) + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def botinfo( + self, + interaction: discord.Interaction + ): + "Get information about the bot. i.e. number of servers, creation date, etc." + + embed = discord.Embed( + title=f"Bot Information", + color=BOT_COLOR + ) + #embed.set_thumbnail(url=self.bot.user.avatar.url) + embed.add_field(name="Servers: ", value = f"`{len(self.bot.guilds):,}`"), + embed.add_field(name="Account name: ", value=f"`{str(self.bot.user.name)}`") + embed.add_field(name="Discord ID: ", value=f"`{str(self.bot.user.id)}`") + embed.add_field(name="Bot created at: ", value=f"`{self.bot.user.created_at.strftime('%Y-%m-%d')}`"), + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + +async def setup(bot): + await bot.add_cog(Info(bot))
\ No newline at end of file diff --git a/code/cogs/modals.py b/code/cogs/modals.py new file mode 100644 index 0000000..e6252f7 --- /dev/null +++ b/code/cogs/modals.py @@ -0,0 +1,102 @@ +from discord.ext import commands +import discord +from discord import app_commands + +from global_variables import BUG_CHANNEL_ID, BOT_COLOR, FEEDBACK_CHANNEL_ID + + +class BugReport(discord.ui.Modal, title='Report a bug'): + def __init__(self, bot): + super().__init__() + self.bot = bot + + name = discord.ui.TextInput( + label='Discord name & tag', + placeholder='EX: Bob#0001...', + ) + command = discord.ui.TextInput( + label='Command with error', + placeholder='EX: blackjack...', + required=True + ) + report = discord.ui.TextInput( + label='A detailed report of the bug', + style=discord.TextStyle.long, + placeholder='Type your report here...', + required=True, + max_length=500, + ) + + async def on_submit(self, interaction: discord.Interaction): + await interaction.response.send_message(f'Thanks for your bug report. We will get back to you as soon as possible', ephemeral=True) + channel = self.bot.get_channel(BUG_CHANNEL_ID) + + embed = discord.Embed( + title="Bug Report", + description=f"Submitted by {self.name}", + color=BOT_COLOR + ) + embed.add_field(name="Command with issue:", value=f"{self.command}", inline=False) + embed.add_field(name="Report:", value=f"{self.report}", inline=False) + + await channel.send(embed=embed) + + +class FeedbackForm(discord.ui.Modal, title='Give feedback about the bot'): + def __init__(self, bot): + super().__init__() + self.bot = bot + + name = discord.ui.TextInput( + label='Discord name & tag', + placeholder='EX: Bob#0001...', + ) + positive = discord.ui.TextInput( + label='What do you like about the bot?', + style=discord.TextStyle.long, + placeholder='Your response here...', + required=True, + max_length = 500 + ) + negative = discord.ui.TextInput( + label='What should be changed about the bot?', + style=discord.TextStyle.long, + placeholder='Your response here...', + required=True, + max_length=500 + ) + + async def on_submit(self, interaction: discord.Interaction): + await interaction.response.send_message(f'Thank you for your feedback. We love hearing from users!', ephemeral=True) + channel = self.bot.get_channel(FEEDBACK_CHANNEL_ID) + + embed = discord.Embed( + title="Bot Feedback", + description=f"Submitted by {self.name}", + color=BOT_COLOR + ) + embed.add_field(name = "Positive:", value = f"{self.positive}", inline=False) + embed.add_field(name = "Negative:", value = f"{self.negative}", inline=False) + + await channel.send(embed=embed) + + +class Modals(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @app_commands.command() + async def bug(self, interaction: discord.Interaction): + "Send a bug report to the developer" + await interaction.response.send_modal(BugReport(self.bot)) + + + @app_commands.command() + async def feedback(self, interaction: discord.Interaction): + "Send bot feeback to the developer" + await interaction.response.send_modal(FeedbackForm(self.bot)) + + +async def setup(bot): + await bot.add_cog(Modals(bot))
\ No newline at end of file diff --git a/code/cogs/moderation.py b/code/cogs/moderation.py new file mode 100644 index 0000000..ea99866 --- /dev/null +++ b/code/cogs/moderation.py @@ -0,0 +1,96 @@ +import discord +from discord.ext import commands +import datetime +from discord import app_commands + +class Moderation(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @app_commands.default_permissions(manage_messages=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_messages=True) + @app_commands.describe(amount='Number of messages you would like deleted') + async def purge( + self, + interaction: discord.Interaction, + amount: app_commands.Range[int, 1, 100] + ): + "Delete the specified number of messages from the channel" + await interaction.channel.purge(limit=amount+1) + await interaction.response.send_message(f"{amount} {'messages' if amount > 1 else 'message'} deleted") + + + @app_commands.default_permissions(kick_members=True) + @app_commands.command() + @app_commands.checks.has_permissions(kick_members=True) + @app_commands.describe(member='Member you would like to kick') + @app_commands.describe(reason='Reason for kicking the member') + async def kick( + self, + interaction: discord.Interaction, + member: discord.Member, + reason: str + ): + "Kick a member from your server" + await member.kick(reason=reason) + embed = discord.Embed( + title=f"{member} Kicked", + description=f"{interaction.user.mention} has kicked {member.mention} for `\"{reason}\"`.", + color=discord.Color.orange() + ) + embed.set_thumbnail(url=member.avatar.url) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.default_permissions(ban_members=True) + @app_commands.command() + @app_commands.checks.has_permissions(ban_members=True) + @app_commands.describe(member='Member you would like to ban') + @app_commands.describe(reason='Reason for banning the member') + async def ban( + self, + interaction: discord.Interaction, + member: discord.Member, + reason: str + ): + "Ban a member from your server" + await member.ban(reason=reason) + embed = discord.Embed( + title=f"{member} Banned", + description=f"{interaction.user.mention} has banned {member.mention} for `\"{reason}\"`.", + color=discord.Color.red() + ) + embed.set_thumbnail(url=member.avatar.url) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.default_permissions(kick_members=True) + @app_commands.command() + @app_commands.checks.has_permissions(kick_members=True) + @app_commands.describe(member='Member you would like to softban') + @app_commands.describe(reason='Reason for softbanning the member') + async def softban( + self, + interaction: discord.Interaction, + member: discord.Member, + reason: str + ): + "Ban and then immediately unban a member" + await member.ban(reason=reason) + await member.unban() + embed = discord.Embed( + title=f"{member} Softbanned", + description=f"{interaction.user.mention} has softbanned {member.mention} for `\"{reason}\"`.", + color=discord.Color.orange() + ) + embed.set_thumbnail(url=member.avatar.url) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + +async def setup(bot): + await bot.add_cog(Moderation(bot))
\ No newline at end of file diff --git a/code/cogs/music.py b/code/cogs/music.py new file mode 100644 index 0000000..e6f9ef0 --- /dev/null +++ b/code/cogs/music.py @@ -0,0 +1,584 @@ +import re +import discord +import lavalink +from discord.ext import commands +import math +import requests +import datetime +from discord import app_commands +from custom_source import CustomSource +from global_variables import BOT_COLOR + +url_rx = re.compile(r'https?://(?:www\.)?.+') + +class LavalinkVoiceClient(discord.VoiceClient): + """ + This is the preferred way to handle external voice sending + This client will be created via a cls in the connect method of the channel + see the following documentation: + https://discordpy.readthedocs.io/en/latest/api.html#voiceprotocol + """ + + def __init__(self, client: commands.Bot, channel: discord.abc.Connectable): + self.client = client + self.channel = channel + if hasattr(self.client, 'lavalink'): + self.lavalink = self.client.lavalink + else: + self.client.lavalink = lavalink.Client(client.user.id) + self.client.lavalink.add_node( + '127.0.0.1', + 2333, + 'youshallnotpass', + 'us-central', + 'default-node' + ) + self.lavalink = self.client.lavalink + + async def on_voice_server_update(self, data): + lavalink_data = { + 't': 'VOICE_SERVER_UPDATE', + 'd': data + } + await self.lavalink.voice_update_handler(lavalink_data) + + async def on_voice_state_update(self, data): + lavalink_data = { + 't': 'VOICE_STATE_UPDATE', + 'd': data + } + await self.lavalink.voice_update_handler(lavalink_data) + + async def connect(self, *, timeout: float, reconnect: bool, self_deaf: bool = True, self_mute: bool = False) -> None: + self.lavalink.player_manager.create(guild_id=self.channel.guild.id) + await self.channel.guild.change_voice_state(channel=self.channel, self_mute=self_mute, self_deaf=self_deaf) + + async def disconnect(self, *, force: bool) -> None: + player = self.lavalink.player_manager.get(self.channel.guild.id) + if not force and not player.is_connected: + return + + await self.channel.guild.change_voice_state(channel=None) + player.channel_id = None + self.cleanup() + + +class Music(commands.Cog): + def __init__(self, bot): + self.bot = bot + + if not hasattr(bot, 'lavalink'): # This ensures the client isn't overwritten during cog reloads. + bot.lavalink = lavalink.Client(self.bot.user.id) + bot.lavalink.add_node( + '127.0.0.1', + 2333, + 'youshallnotpass', + 'us-central', + 'default-node' + ) # Host, Port, Password, Region, Name + + self.lavalink: lavalink.Client = bot.lavalink + self.lavalink.add_event_hooks(self.track_hook) + + def cog_unload(self): + """ Cog unload handler. This removes any event hooks that were registered. """ + self.lavalink._event_hooks.clear() + + async def ensure_before(self, interaction): + """ Command before-invoke handler. """ + guild_check = interaction.guild is not None + + if guild_check: + await self.ensure_voice(interaction) + + return guild_check + + async def ensure_voice(self, interaction: discord.Interaction): + """ This check ensures that the bot and command author are in the same voicechannel. """ + player = self.lavalink.player_manager.create(interaction.guild.id) + should_connect = interaction.command.name in ('play',) + + if not interaction.user.voice or not interaction.user.voice.channel: + await interaction.response.send_message('Join a voicechannel first.', ephemeral=True) + raise ZeroDivisionError + + if not player.is_connected: + if not should_connect: + await interaction.response.send_message('Not connected.', ephemeral=True) + raise ZeroDivisionError + + permissions = interaction.user.voice.channel.permissions_for(interaction.user) + + if not permissions.connect or not permissions.speak: + await interaction.response.send_message('I need the `CONNECT` and `SPEAK` permissions.', ephemeral=True) + raise ZeroDivisionError + + player.store('channel', interaction.channel.id) + await interaction.user.voice.channel.connect(cls=LavalinkVoiceClient) + else: + if int(player.channel_id) != interaction.user.voice.channel.id: + await interaction.response.send_message('You need to be in my voicechannel.', ephemeral=True) + raise ZeroDivisionError + + async def track_hook(self, event): + if isinstance(event, lavalink.events.QueueEndEvent): + guild_id = event.player.guild_id + guild = self.bot.get_guild(guild_id) + await guild.voice_client.disconnect(force=True) + + @commands.Cog.listener() + async def on_voice_state_update(self, member, before, after): + if before.channel and member == self.bot.user: + if after.channel is None: + player = self.lavalink.player_manager.get(member.guild.id) + player.queue.clear() + await player.stop() + guild = member.guild + try: + await guild.voice_client.disconnect(force=True) + player.shuffle = not player.shuffle if player.shuffle else player.shuffle + except AttributeError: + pass + + + @app_commands.command() + @app_commands.describe(name="Name or link of song") + async def play( + self, + interaction: discord.Interaction, + name: str + ): + "Play a song from your favorite music provider" + await self.ensure_before(interaction) + player = self.lavalink.player_manager.get(interaction.guild.id) + query = name + + # Below begins the start of the search for Spotify links - we must check for playlist, albums, and tracks + # We use a custom source in order to provide us with the correct information and streams + if "open.spotify.com/playlist" in query: + playlist_id = query.split("playlist/")[1].split("?si=")[0] + playlist_url = f"https://api.spotify.com/v1/playlists/{playlist_id}" + headers = {"Authorization": f"Bearer {self.bot.access_token}"} + response = requests.get(playlist_url, headers=headers) + if response.status_code == 200: + playlist = response.json() + + embed = discord.Embed( + title = "Playlist Queued", + description = f"**{playlist['name']}** from **{playlist['owner']['display_name']}**\n` {len(playlist['tracks']['items'])} ` tracks\n\nQueued by: {interaction.user.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=playlist['images'][0]['url']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + tracks = await CustomSource.load_playlist(self, interaction.user, playlist) + for track in tracks['tracks']: + player.add(requester=interaction.user, track=track) + + if "open.spotify.com/album" in query: + album_id = query.split("album/")[1] + album_url = f"https://api.spotify.com/v1/albums/{album_id}" + headers = {"Authorization": f"Bearer {self.bot.access_token}"} + response = requests.get(album_url, headers=headers) + if response.status_code == 200: + album = response.json() + + embed = discord.Embed( + title = "Album Queued", + description = f"**{album['name']}** by **{album['artists'][0]['name']}**\n` {len(album['tracks']['items'])} ` tracks\n\nQueued by: {interaction.user.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=album['images'][0]['url']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + tracks = await CustomSource.load_album(self, interaction.user, album) + for track in tracks['tracks']: + player.add(requester=interaction.user, track=track) + + if "open.spotify.com/track" in query: + track_id = query.split("track/")[1] + track_url = f"https://api.spotify.com/v1/tracks/{track_id}" + headers = {"Authorization": f"Bearer {self.bot.access_token}"} + response = requests.get(track_url, headers=headers) + if response.status_code == 200: + track = response.json() + + embed = discord.Embed( + title = "Track Queued", + description = f"**{track['name']}** by **{track['artists'][0]['name']}**\n\nQueued by: {interaction.user.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=track['album']['images'][0]['url']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + track_ = await CustomSource.load_item(self, interaction.user, track) + player.add(requester=interaction.user, track=track_.tracks[0]) + + if 'open.spotify.com/artists' in query: + return await interaction.response.send_message("Sorry, I can't play artists. You must provide a song/album/playlist.", ephemeral=True) + + if 'open.spotify.com' in query: + if not player.is_playing: + await player.play() + return + + # Now begins the soundcloud section, this can be just like the youtube section + if not url_rx.match(query): + query = f'ytsearch:{query}' + results = await player.node.get_tracks(query) + + # Below is for YouTube search, which is the default and is used when no link is provided + if not url_rx.match(query) and not results or not results['tracks']: + query = f'scsearch:{query}' + results = await player.node.get_tracks(query) + + if not results or not results['tracks']: + return await interaction.response.send_message('Nothing found!', ephemeral=True) + + embed = discord.Embed(color=BOT_COLOR) + + if results['loadType'] == 'PLAYLIST_LOADED': + tracks = results['tracks'] + + for track in tracks: + track_ = lavalink.AudioTrack(track, interaction.user.id, extra=f"https://img.youtube.com/vi/{track['info']['identifier']}/hqdefault.jpg") + player.add(requester=interaction.user, track=track_) + + embed.title = 'Playlist Queued!' + embed.description = f"**{results['playlistInfo']['name']}**\n` {len(tracks)} ` tracks\n\nQueued by: {interaction.user.mention}" + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + else: + track = results['tracks'][0] + embed.title = 'Track Queued' + embed.description = f"**{track['info']['title']}** by **{track['info']['author']}**\n\nQueued by: {interaction.user.mention}" + embed.set_thumbnail(url=f"https://img.youtube.com/vi/{track['info']['identifier']}/hqdefault.jpg") + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + + track_ = lavalink.AudioTrack(track, interaction.user.id, recommended=True, extra=f"https://img.youtube.com/vi/{track['info']['identifier']}/hqdefault.jpg") + player.add(requester=interaction.user, track=track_) + + await interaction.response.send_message(embed=embed) + + # We don't want to call .play() if the player is playing as that will + # effectively skip the current track + if not player.is_playing: + await player.play() + + + @app_commands.command() + async def stop( + self, + interaction: discord.Interaction + ): + "Disconnects the bot from the voice channel and clears the queue" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.is_connected: + embed = discord.Embed( + title="No Channel", + description="I am not currently connected to any voice channel.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + player.queue.clear() + await player.stop() + player.shuffle = not player.shuffle if player.shuffle else player.shuffle + guild = interaction.guild + await guild.voice_client.disconnect(force=True) + embed = discord.Embed( + title="Queue Cleared and Music Stopped", + description=f"Thank you for using Aqua Bot :wave:\n\nIssued by: {interaction.user.mention}", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def clear( + self, + interaction: discord.Interaction + ): + "Clear the current queue of songs" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.is_connected: + embed = discord.Embed( + title="No Channel", + description="I am not currently connected to any voice channel.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + player.queue.clear() + embed = discord.Embed( + title="Queue Cleared", + description = f"The queue has been cleared of all songs!\n\nIssued by: {interaction.user.mention}", + color=0x0088a9 + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def skip( + self, + interaction: discord.Interaction + ): + "Skips the song that is currently playing" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.is_playing: + embed = discord.Embed( + title="Nothing Playing", + description="Nothing is currently playing, so I can't skip anything.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + await player.skip() + if not player.is_playing: + embed = discord.Embed( + title="Track Skipped", + description=f"The queue is now empty, so I have left the voice channel. Thank you for using Aqua Bot.\n\nIssued by: {interaction.user.mention}", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed) + + embed = discord.Embed( + title="Track Skipped", + description = f"**Now Playing: [{player.current.title}]({player.current.uri})**\n\nQueued by: {player.current.requester.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=player.current.extra['extra']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def pause( + self, + interaction: discord.Interaction + ): + "Pauses the song that is currently playing" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.is_playing: + embed = discord.Embed( + title="Nothing Playing", + description="Nothing is currently playing, so I can't pause anything.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + await player.set_pause(pause=True) + embed = discord.Embed( + title = f"Music Now Paused ⏸️", + description = f"**[{player.current.title}]({player.current.uri})**\n\nQueued by: {player.current.requester.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=player.current.extra['extra']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def resume( + self, + interaction: discord.Interaction + ): + "Resumes the paused song" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.is_playing: + embed = discord.Embed( + title="Nothing Paused", + description="Nothing is currently paused, so I can't resume anything.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + await player.set_pause(pause=False) + embed = discord.Embed( + title=f"Music Now Resumed ⏯️", + description=f"**[{player.current.title}]({player.current.uri})**\n\nQueued by: {player.current.requester.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=player.current.extra['extra']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + @app_commands.describe(page="Queue page number - leave blank if you are unsure") + async def queue( + self, + interaction: discord.Interaction, + page: int = 1 + ): + "See the current queue of songs" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.queue: + embed = discord.Embed( + title="Nothing Queued", + description="Nothing is currently in the queue, add a song with the `/play` command.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + items_per_page = 10 + pages = math.ceil(len(player.queue) / items_per_page) + + start = (page - 1) * items_per_page + end = start + items_per_page + + queue_list = '' + for index, track in enumerate(player.queue[start:end], start=start): + # Change ms duration to hour, min, sec in the format of 00:00:00 + track_duration = lavalink.utils.format_time(track.duration) + # If the track is less than an hour, remove the hour from the duration + if track_duration.split(':')[0] == '00': + track_duration = track_duration[3:] + + queue_list += f"`{index+1}. ` [{track.title}]({track.uri}) - {track.author} `({track_duration})`\n" + + embed = discord.Embed( + title=f"Queue for {interaction.guild.name}", + description=f'**{len(player.queue)} tracks total**\n\n{queue_list}', + color=BOT_COLOR + ) + embed.set_footer(text=f'Viewing page {page}/{pages}') + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def np( + self, + interaction: discord.Interaction + ): + "Show what song is currently playing" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.is_playing: + embed = discord.Embed( + title="Nothing Playing", + description="Nothing is currently playing, play a song with the `/play` command.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + time_in = str(datetime.timedelta(milliseconds=player.position))[:-7] + total_duration = lavalink.utils.format_time(player.current.duration) + # If total_duration has no hours, then remove the hour part from both times + if total_duration.split(":")[0] == "00": + time_in = time_in[2:] + total_duration = total_duration[3:] + + embed= discord.Embed( + title="Now Playing 🎶", + description=f"**[{player.current.title}]({player.current.uri})**\n{f'` {time_in}/{total_duration} `'}\n\nQueued by: {player.current.requester.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=player.current.extra['extra']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + @app_commands.describe(number='Song number to have removed') + async def remove( + self, + interaction: discord.Interaction, + number: int + ): + "Removes the specified song from the queue" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.queue: + embed = discord.Embed( + title="Nothing Queued", + description="Nothing is currently in the queue, so there is nothing for me to remove.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed) + + if number > len(player.queue) or number < 1: + return await interaction.response.send_message('The number entered is not a number within the queue - please try again!', ephemeral=True) + + index = number - 1 + removed_title = player.queue[index].title + removed_url = player.queue[index].uri + player.queue.pop(index) + + embed = discord.Embed( + title="Song Removed from Queue", + description=f"**Song Removed - [{removed_title}]({removed_url})**\n\nIssued by: {interaction.user.mention}", + color=BOT_COLOR + ) + embed.set_thumbnail(url=player.current.extra['extra']) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def shuffle( + self, + interaction: discord.Interaction + ): + "Plays the songs in the queue in a randomized order, until turned off" + await self.ensure_before(interaction) + + player = self.lavalink.player_manager.get(interaction.guild.id) + + if not player.is_playing: + embed = discord.Embed( + title="Nothing Playing", + description="Nothing is currently playing, therefore I cannot shuffle the music.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed, ephemeral=True) + + player.shuffle = not player.shuffle + + embed = discord.Embed( + title=f"{'Shuffle Enabled 🔀' if player.shuffle else 'Shuffle Disabled 🔀'}", + description=f"{'All music will now be shuffled.' if player.shuffle else 'Music will no longer be shuffled.'}", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + +async def setup(bot): + await bot.add_cog(Music(bot))
\ No newline at end of file diff --git a/code/cogs/mute.py b/code/cogs/mute.py new file mode 100644 index 0000000..cf7381e --- /dev/null +++ b/code/cogs/mute.py @@ -0,0 +1,282 @@ +import discord +from discord.ext import commands, tasks +import datetime +from discord import app_commands + +from global_variables import CONNECTION, BOT_COLOR + + +class Mute(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + + def cog_load(self): + self.mute_check.start() + + @tasks.loop(seconds=60) + async def mute_check(self): + cur = CONNECTION.cursor() + cur.execute("SELECT * FROM tempmute WHERE time < %s", (datetime.datetime.now(),)) + data = cur.fetchall() + for row in data: + try: + guild_id = row[0] + user_id = row[1] + role_id = row[2] + guild = await self.bot.fetch_guild(guild_id) + user = await guild.fetch_member(user_id) + role = guild.get_role(role_id) + await user.remove_roles(role) + cur.execute("DELETE FROM tempmute WHERE guild_id = %s AND user_id = %s", (guild_id, user_id)) + CONNECTION.commit() + except: + pass + + + @app_commands.default_permissions(manage_roles=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_roles=True) + @app_commands.describe(role_name='Name of your servers muted role') + async def setmute( + self, + interaction: discord.Interaction, + role_name: discord.Role + ): + "Set the role for users to be given when muted" + guild_id = interaction.user.guild.id + role = role_name + role_id = role.id + + cur = CONNECTION.cursor() + cur.execute("SELECT role_id FROM mute WHERE guild_id = %s", (guild_id,)) + role_id = cur.fetchone() + if role_id != None: + cur.execute("UPDATE mute SET role_id = %s WHERE guild_id = %s", (role.id, guild_id)) + CONNECTION.commit() + + else: + cur.execute("INSERT INTO mute (role_id, guild_id) VALUES(%s, %s)", (role.id, guild_id)) + CONNECTION.commit() + + embed = discord.Embed( + title = "Mute Role Set", + description = f"The mute role for {interaction.guild.name} has been set to: <@&{role.id}>", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.default_permissions(manage_roles=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_roles=True) + async def delmute( + self, + interaction: discord.Interaction + ): + "Delete the role set to be given to muted users" + guild_id = interaction.user.guild.id + cur = CONNECTION.cursor() + cur.execute("SELECT role_id FROM mute WHERE guild_id = %s", (guild_id,)) + data = cur.fetchone() + if data: + cur.execute("DELETE FROM mute WHERE guild_id = %s", (guild_id,)) + CONNECTION.commit() + + embed = discord.Embed( + title = "Mute Role Deleted", + description = f"The mute role for {interaction.user.guild.name} has been deleted from my database.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + else: + embed = discord.Embed( + title = "Mute Role Not Set", + description = f"The mute role is not set, therefore there is no role I can delete.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, ephemeral=True) + + + @app_commands.default_permissions(manage_roles=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_roles=True) + async def muterole( + self, + interaction: discord.Interaction + ): + "See the current role set for when users are muted" + guild_id = interaction.user.guild.id + cur = CONNECTION.cursor() + cur.execute("SELECT role_id FROM mute WHERE guild_id = %s", (guild_id,)) + data = cur.fetchone() + if data: + role = interaction.guild.get_role(data[0]) + if role: + embed = discord.Embed( + title=f"Mute Role for {interaction.user.guild.name}", + description=f"The role given to members who are muted in this server is: <@&{role.id}>", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed) + + else: + embed = discord.Embed( + title="Mute Role Not Found", + description=f"You have previously set a mute role, but it seems that the role you set has since been deleted. Please add a new mute role with the `/setmute` command.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + return await interaction.response.send_message(embed=embed) + + else: + embed = discord.Embed( + title = "No Role Set", + description = f"It seems you haven't set a muted role yet. Please go do that with `/setmute` before running this command.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, ephemeral=True) + + + @app_commands.default_permissions(manage_roles=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_roles=True) + @app_commands.describe(member='Name of the member you want to temporarily mute') + @app_commands.describe(time='Amount of time (in hours) to mute the member') + async def tempmute( + self, + interaction: discord.Interaction, + member: discord.Member, + time: app_commands.Range[int, 1, None] + ): + "Mute a user for a specified amount of time" + try: + guild_id = interaction.user.guild.id + cur = CONNECTION.cursor() + cur.execute("SELECT role_id FROM mute WHERE guild_id = %s", (guild_id,)) + data = cur.fetchone() + role_id = data[0] + role_name = interaction.user.guild.get_role(role_id) + role = discord.utils.get(interaction.user.guild.roles, name=f"{role_name}") + + await member.add_roles(role) + embed = discord.Embed( + title=f"Temporarily Muted {member}", + description=f"{interaction.user.mention} has temporarily muted {member.mention} for {time} hours.", + color=discord.Color.orange() + ) + embed.set_thumbnail(url=member.avatar.url) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + cur = CONNECTION.cursor() + cur.execute("INSERT INTO tempmute (guild_id, user_id, role_id, time) VALUES (%s, %s, %s, %s)", (guild_id, member.id, role.id, (datetime.datetime.now() + datetime.timedelta(hours=time)))) + CONNECTION.commit() + + except TypeError: + embed = discord.Embed( + title = "No Role Set", + description = f"It seems you haven't set a muted role yet. Please go do that with `/setmute` before running this command.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, ephemeral=True) + + + @app_commands.default_permissions(manage_roles=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_roles=True) + @app_commands.describe(member='Name of the member you want to mute') + @app_commands.describe(reason='Reason for muting the member') + async def mute( + self, + interaction: discord.Interaction, + member: discord.Member, + reason: str + ): + "Mutes a user for an indefinite amount of time" + try: + guild_id = interaction.user.guild.id + cur = CONNECTION.cursor() + cur.execute("SELECT role_id FROM mute WHERE guild_id = %s", (guild_id,)) + data = cur.fetchone() + role_id = data[0] + role_name = interaction.user.guild.get_role(role_id) + role = discord.utils.get(interaction.user.guild.roles, name=f"{role_name}") + + await member.add_roles(role) + embed = discord.Embed( + title=f"Muted {member}", + description=f"{interaction.user.mention} has successfully muted {member.mention} for `\"{reason}\"`.", + color=discord.Color.red() + ) + embed.set_thumbnail(url=member.avatar.url) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + except TypeError or AttributeError: + embed = discord.Embed( + title = "No Role Set", + description = f"It seems you haven't set a muted role yet. Please go do that with `/setmute` before running this command.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, ephemeral=True) + + + @app_commands.default_permissions(manage_roles=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_roles=True) + @app_commands.describe(member='Name of the member you want to unmute') + async def unmute( + self, + interaction: discord.Interaction, + member: discord.Member + ): + "Unmute a specified member" + try: + guild_id = interaction.user.guild.id + cur = CONNECTION.cursor() + cur.execute("SELECT role_id FROM mute WHERE guild_id = %s", (guild_id,)) + data = cur.fetchone() + role_id = data[0] + role_name = interaction.user.guild.get_role(role_id) + role = discord.utils.get(interaction.user.guild.roles, name=f"{role_name}") + + if role in member.roles: + await member.remove_roles(role) + embed = discord.Embed( + title=f"Unmuted {member}", + description=f"{interaction.user.mention} has successfully unmuted {member.mention}.", + color=discord.Color.green() + ) + embed.set_thumbnail(url=member.avatar.url) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + else: + embed = discord.Embed( + title="User Isn't Muted", + description=f"{member.mention} isn't muted, therefore I cannot unmute them. Maybe you meant to mute them with the `mute` command?", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, ephemeral=True) + + except TypeError or AttributeError: + embed = discord.Embed( + title="No Role Set", + description=f"It seems you haven't set a muted role yet. Please go do that with `/setmute` before running this command.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Mute(bot))
\ No newline at end of file diff --git a/code/cogs/owner_info_commands.py b/code/cogs/owner_info_commands.py new file mode 100644 index 0000000..aae0fd4 --- /dev/null +++ b/code/cogs/owner_info_commands.py @@ -0,0 +1,52 @@ +from discord.ext import commands +import discord +import aiosqlite + +from global_variables import BOT_COLOR + + +class UserCount(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @commands.command() + @commands.dm_only() + @commands.is_owner() + async def info(self, ctx: commands.Context): + total_guilds = {} + + for guild in self.bot.guilds: + total_guilds[guild.name] = guild.member_count + + # Sort the dictionary by value descending + total_guilds = dict(sorted(total_guilds.items(), key=lambda item: item[1], reverse=True)) + + total_members = 0 + + for guild in total_guilds: + total_members += total_guilds[guild] + + cur = await aiosqlite.connect("./code/count/count.db") + count = await cur.execute("SELECT count FROM count") + count = await count.fetchone() + await cur.close() + if count is None: + count = 0 + else: + count = count[0] + + embed = discord.Embed( + title="User Count", + description=f"Total Members: `{total_members:,}`\nTotal Guilds: `{len(self.bot.guilds):,}`\nTotal Commands Run: `{count:,}`", + color=BOT_COLOR + ) + # Add the top 5 guilds to the embed + for guild in list(total_guilds)[:5]: + embed.add_field(name=guild, value=f"```{total_guilds[guild]:,}```", inline=False) + + await ctx.send(embed=embed) + + +async def setup(bot): + await bot.add_cog(UserCount(bot))
\ No newline at end of file diff --git a/code/cogs/profile.py b/code/cogs/profile.py new file mode 100644 index 0000000..4f76c66 --- /dev/null +++ b/code/cogs/profile.py @@ -0,0 +1,691 @@ +import discord +import datetime +from discord.ext import commands +import psycopg2 +from discord import app_commands + +from global_variables import CONNECTION, BOT_COLOR +from bot import InsufficientFundsException +from database import Database + + +class Economy: + def __init__(self, bot): + self.bot = bot + self.economy = Database(bot) + + async def check_bet( + self, + user_id, + bet, + ): + bet = int(bet) + if bet <= 0: + raise commands.errors.BadArgument() + current = (await self.economy.get_entry(user_id))[1] + if bet > current: + raise InsufficientFundsException() + +#BEGIN CODE FOR RANK PURCHASING + +class AfterRankPurchase(discord.ui.View): + def __init__(self, bot, *, timeout = 180.0): + super().__init__(timeout=timeout) + self.bot = bot + self.economy = Database(bot) + self.value = None + + @discord.ui.button(label='Main Page', style=discord.ButtonStyle.blurple, row=2) + async def main_page(self, interaction: discord.Interaction, button: discord.ui.Button): + user_id = interaction.user.id + profile = await self.economy.get_entry(user_id) + balance = profile[1] + + cur = CONNECTION.cursor() + cur.execute("SELECT rank_name FROM profile WHERE user_id = %s ORDER BY rank_int DESC", (user_id,)) + data = cur.fetchall() + if data: + names = ', '.join([str(i[0]) for i in data]) + + else: + names = "No ranks" + + embed = discord.Embed( + title="Shop", + description=f"Choose from one of the categories below in order to shop for items \n\nBalance: **${balance:,}** \n\nRanks: **{names}**", + color=BOT_COLOR + ) + view = ShopView(self.bot) + await interaction.response.edit_message(embed=embed, view=view) + + +class ConfirmRankPurchase(discord.ui.View): + def __init__(self, bot, bet, rank_value, rank_name, *, timeout = 180.0): + super().__init__(timeout=timeout) + self.bot = bot + self.bet = bet + self.rank_int = int(rank_value) + self.rank_name = str(rank_name) + self.check = Economy(bot) + self.economy = Database(bot) + self.add_item(RankDropdown(bot)) + + + @discord.ui.button(label='Yes', style=discord.ButtonStyle.green, row=2) + async def yes(self, interaction: discord.Interaction, button: discord.ui.Button): + user_id = interaction.user.id + + cur = CONNECTION.cursor() + try: + await self.check.check_bet(user_id, self.bet) + cur.execute("INSERT INTO profile (user_id, rank_name, rank_int) VALUES(%s,%s,%s)", (user_id, self.rank_name, self.rank_int)) + CONNECTION.commit() + await self.economy.add_money(user_id, self.bet*-1) + + embed = discord.Embed( + title="Purchase Successful", + description=f"Your purchase was successful. In order to purchase more items, please click the main page button below.", + color=BOT_COLOR + ) + view = AfterRankPurchase(self.bot) + await interaction.response.edit_message(embed=embed, view=view) + + except psycopg2.errors.UniqueViolation: + embed = discord.Embed( + title="Rank Already Owned", + description=f"You already have that rank and therefore cannot buy it again. Try purchasing another rank.", + color=BOT_COLOR + ) + view = RankView(self.bot) + return await interaction.response.edit_message(embed=embed, view=view) + + except InsufficientFundsException: + embed = discord.Embed( + title="Not Enough Money", + description=f"You do not have enough money to make that purchase, come back once you've earned some more money.", + color=BOT_COLOR + ) + view = RankView(self.bot) + return await interaction.response.edit_message(embed=embed, view=view) + + + @discord.ui.button(label='No', style=discord.ButtonStyle.red, row=2) + async def no(self, interaction: discord.Interaction, button: discord.ui.Button): + embed = discord.Embed( + title="Purchase Cancelled, Taken Back to Shop", + description=f"Choose from one of the categories below in order to shop for items.", + color=BOT_COLOR + ) + view = ShopView(self.bot) + await interaction.response.edit_message(embed=embed, view=view) + + +class RankDropdown(discord.ui.Select): + def __init__(self, bot): + self.bot = bot + + options = [ + discord.SelectOption(label='Copper III', description="100,000", emoji = "<:copper_3:908535582534299688>"), + discord.SelectOption(label='Copper II', description="200,000", emoji = "<:copper_2:908535594714558524>"), + discord.SelectOption(label='Copper I', description="300,000", emoji = "<:copper_1:908535605644918895>"), + + discord.SelectOption(label='Bronze III', description="100,000,000", emoji = "<:bronze_3:908535616650760222>"), + discord.SelectOption(label='Bronze II', description="200,000,000", emoji = "<:bronze_2:908535628503863296>"), + discord.SelectOption(label='Bronze I', description="300,000,000", emoji = "<:bronze_1:908535639606198292>"), + + discord.SelectOption(label='Silver III', description="100,000,000,000", emoji = "<:silver_3:908535654667911168>"), + discord.SelectOption(label='Silver II', description="200,000,000,000", emoji = "<:silver_2:908535667263434782>"), + discord.SelectOption(label='Silver I', description="300,000,000,000", emoji = "<:silver_1:908535680064442398>"), + + discord.SelectOption(label='Gold III', description="100,000,000,000,000", emoji = "<:gold_3:908535691137388554>"), + discord.SelectOption(label='Gold II', description="200,000,000,000,000", emoji = "<:gold_2:908535705154764870>"), + discord.SelectOption(label='Gold I', description="300,000,000,000,000", emoji = "<:gold_1:908535742224027758>"), + + discord.SelectOption(label='Platinum III', description="100,000,000,000,000,000", emoji = "<:platinum_3:908535751900282880>"), + discord.SelectOption(label='Platinum II', description="200,000,000,000,000,000", emoji = "<:platinum_2:908535764629999656>"), + discord.SelectOption(label='Platinum I', description="300,000,000,000,000,000", emoji = "<:platinum_1:908535773689679932>"), + + discord.SelectOption(label='Diamond', description="123,456,789,000,000,000,000", emoji = "<:diamond:908535791700037702>"), + + discord.SelectOption(label='Champion', description="999,999,999,999,999,999,999,999,999", emoji = "<:champion:908535801338540042>"), + + ] + + super().__init__(placeholder='Choose a rank...', min_values=1, max_values=1, options=options) + + async def callback(self, interaction: discord.Interaction): + if self.values[0] == 'Copper III': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Copper III` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 1 + rank_name = 'Copper III' + bet = 100000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Copper II': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Copper II` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 2 + rank_name = 'Copper II' + bet = 200000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Copper I': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Copper I` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 3 + rank_name = 'Copper I' + bet = 300000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Bronze III': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Bronze III` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 4 + rank_name = 'Bronze III' + bet = 100000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Bronze II': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Bronze II` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 5 + rank_name = 'Bronze II' + bet = 200000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Bronze I': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Bronze I` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 6 + rank_name = 'Bronze I' + bet = 300000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Silver III': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Silver III` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 7 + rank_name = 'Silver III' + bet = 100000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Silver II': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Silver II` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 8 + rank_name = 'Silver II' + bet = 200000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Silver I': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Silver I` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 9 + rank_name = 'Silver I' + bet = 300000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Gold III': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Gold III` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 10 + rank_name = 'Gold III' + bet = 100000000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Gold II': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Gold II` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 11 + rank_name = 'Gold II' + bet = 200000000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Gold I': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Gold I` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 12 + rank_name = 'Gold I' + bet = 300000000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Platinum III': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Platinum III` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 13 + rank_name = 'Platinum III' + bet = 100000000000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Platinum II': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Platinum II` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 14 + rank_name = 'Platinum II' + bet = 200000000000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Platinum I': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Platinum I` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 15 + rank_name = 'Platinum I' + bet = 300000000000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Diamond': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Diamond` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 16 + rank_name = 'Diamond' + bet = 123456789000000000000 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + if self.values[0] == 'Champion': + embed = discord.Embed( + title="Please Confirm Your Purchase", + description="If you are sure you would like to purchase the `Champion` rank, please click the 'Yes' button below, otherwise click the 'No' button.", + color=BOT_COLOR + ) + + rank_value = 17 + rank_name = 'Champion' + bet = 999999999999999999999999999 + + view = ConfirmRankPurchase(self.bot, bet, rank_value, rank_name) + await interaction.response.edit_message(embed=embed, view=view) + + +class RankView(discord.ui.View): + def __init__(self, bot, *, timeout = 180.0): + super().__init__(timeout=timeout) + self.bot = bot + self.economy = Database(bot) + self.add_item(RankDropdown(bot)) + + + @discord.ui.button(label='Main Page', style=discord.ButtonStyle.blurple, row=2) + async def main_page(self, interaction: discord.Interaction, button: discord.ui.Button): + user_id = interaction.user.id + profile = await self.economy.get_entry(user_id) + balance = profile[1] + + cur = CONNECTION.cursor() + cur.execute("SELECT rank_name FROM profile WHERE user_id = %s ORDER BY rank_int DESC", (user_id,)) + data = cur.fetchall() + if data: + names = ', '.join([str(i[0]) for i in data]) + + else: + names = "No ranks" + + embed = discord.Embed( + title="Shop", + description=f"Choose from one of the categories below in order to shop for items \n\nBalance: **${balance:,}** \n\nRanks: **{names}**", + color=BOT_COLOR + ) + view = ShopView(self.bot) + await interaction.response.edit_message(embed=embed, view=view) + + +##END RANK AND BEGIN PROFILE AND OTHER MAIN SHOP VIEWING + + +class ShopDropdown(discord.ui.Select): + def __init__(self, bot): + self.bot = bot + + options = [ + discord.SelectOption(label='Ranks', description='Buy ranks and show off your wealth'), + ] + + super().__init__(placeholder='Choose a category...', min_values=1, max_values=1, options=options) + + async def callback(self, interaction: discord.Interaction): + user_id = interaction.user.id + cur = CONNECTION.cursor() + cur.execute("SELECT rank_name FROM profile WHERE user_id = %s ORDER BY rank_int DESC", (user_id,)) + data = cur.fetchall() + if data: + names = ([str(i[0]) for i in data]) + + if "Copper III" in names: + copper_iii = "~~Copper III~~ - OWNED" + else: + copper_iii = "Copper III" + + if "Copper II" in names: + copper_ii = "~~Copper II~~ - OWNED" + else: + copper_ii = "Copper II" + + if "Copper I" in names: + copper_i = "~~Copper I~~ - OWNED" + else: + copper_i = "Copper I" + + if "Bronze III" in names: + bronze_iii = "~~Bronze III~~ - OWNED" + else: + bronze_iii = "Bronze III" + + if "Bronze II" in names: + bronze_ii = "~~Bronze II~~ - OWNED" + else: + bronze_ii = "Bronze II" + + if "Bronze I" in names: + bronze_i = "~~Bronze I~~ - OWNED" + else: + bronze_i = "Bronze I" + + if "Silver III" in names: + silver_iii = "~~Silver III~~ - OWNED" + else: + silver_iii = "Silver III" + + if "Silver II" in names: + silver_ii = "~~Silver II~~ - OWNED" + else: + silver_ii = "Silver II" + + if "Silver I" in names: + silver_i = "~~Silver I~~ - OWNED" + else: + silver_i = "Silver I" + + if "Gold III" in names: + gold_iii = "~~Gold III~~ - OWNED" + else: + gold_iii = "Gold III" + + if "Gold II" in names: + gold_ii = "~~Gold II~~ - OWNED" + else: + gold_ii = "Gold II" + + if "Gold I" in names: + gold_i = "~~Gold I~~ - OWNED" + else: + gold_i = "Gold I" + + if "Platinum III" in names: + platinum_iii = "~~Platinum III~~ - OWNED" + else: + platinum_iii = "Platinum III" + + if "Platinum II" in names: + platinum_ii = "~~Platinum II~~ - OWNED" + else: + platinum_ii = "Platinum II" + + if "Platinum I" in names: + platinum_i = "~~Platinum I~~ - OWNED" + else: + platinum_i = "Platinum I" + + if "Diamond" in names: + diamond = "~~Diamond~~ - OWNED" + else: + diamond = "Diamond" + + if "Champion" in names: + champion = "~~Champion~~ - OWNED" + else: + champion = "Champion" + else: + copper_iii = "Copper III" + copper_ii = "Copper II" + copper_i = "Copper I" + bronze_iii = "Bronze III" + bronze_ii = "Bronze II" + bronze_i = "Bronze I" + silver_iii = "Silver III" + silver_ii = "Silver II" + silver_i = "Silver I" + gold_iii = "Gold III" + gold_ii = "Gold II" + gold_i = "Gold I" + platinum_iii = "Platinum III" + platinum_ii = "Platinum II" + platinum_i = "Platinum I" + diamond = "Diamond" + champion = "Champion" + + if self.values[0] == 'Ranks': + embed = discord.Embed( + title="Ranks \nSpend your money in order to get more ranks.", + description="**Purchase a rank by clicking on one of the dropdown menus below, and then confirming your purchase.**", + color=BOT_COLOR + ) + + embed.add_field(name=f"{copper_iii}", value=f"```100,000```", inline=True) + embed.add_field(name=f"{copper_ii}", value=f"```200,000```", inline=True) + embed.add_field(name=f"{copper_i}", value=f"```300,000```", inline=True) + + embed.add_field(name=f"{bronze_iii}", value=f"```100,000,000```", inline=True) + embed.add_field(name=f"{bronze_ii}", value=f"```200,000,000```", inline=True) + embed.add_field(name=f"{bronze_i}", value=f"```300,000,000```", inline=True) + + embed.add_field(name=f"{silver_iii}", value=f"```100,000,000,000\n(100 bil.)```", inline=True) + embed.add_field(name=f"{silver_ii}", value=f"```200,000,000,000\n(200 bil.)```", inline=True) + embed.add_field(name=f"{silver_i}", value=f"```300,000,000,000\n(300 bil.)```", inline=True) + + embed.add_field(name=f"{gold_iii}", value=f"```100,000,000,000,\n000 (100 tril.)```", inline=True) + embed.add_field(name=f"{gold_ii}", value=f"```200,000,000,000,\n000 (200 tril.)```", inline=True) + embed.add_field(name=f"{gold_i}", value=f"```300,000,000,000,\n000 (300 tril.)```", inline=True) + + embed.add_field(name=f"{platinum_iii}", value=f"```100,000,000,000,000,000 (100 quad.)```", inline=True) + embed.add_field(name=f"{platinum_ii}", value=f"```200,000,000,000,000,000 (200 quad.)```", inline=True) + embed.add_field(name=f"{platinum_i}", value=f"```300,000,000,000,000,000 (300 quad.)```", inline=True) + + embed.add_field(name=f"{diamond}", value=f"```123,456,789,000,000,000,000\n(<123 quint.)```", inline=True) + + embed.add_field(name=f"{champion}", value=f"```999,999,999,999,999,999,999,\n999,999 (<999 sept.)```", inline=True) + + view = RankView(self.bot) + await interaction.response.edit_message(embed=embed, view=view) + + +class ShopView(discord.ui.View): + def __init__(self, bot, *, timeout = 180.0): + super().__init__(timeout=timeout) + self.economy = Database(bot) + self.bot = bot + self.add_item(ShopDropdown(bot)) + + + @discord.ui.button(label='Main Page', style=discord.ButtonStyle.blurple, row=2) + async def main_page(self, interaction: discord.Interaction, button: discord.ui.Button): + user_id = interaction.user.id + profile = await self.economy.get_entry(user_id) + balance = profile[1] + + cur = CONNECTION.cursor() + cur.execute("SELECT rank_name FROM profile WHERE user_id = %s ORDER BY rank_int DESC", (user_id,)) + data = cur.fetchall() + if data: + names = ', '.join([str(i[0]) for i in data]) + + else: + names = "No ranks" + + embed = discord.Embed( + title="Shop", + description=f"Choose from one of the categories below in order to shop for items \n\nBalance: **${balance:,}** \n\nRanks: **{names}**", + color=BOT_COLOR + ) + view = ShopView(self.bot) + await interaction.response.edit_message(embed=embed, view=view) + + +#BEGIN CODE FOR PROFILE VIEWING + + +class Profile(commands.Cog): + def __init__(self, bot): + self.bot = bot + self.economy = Database(bot) + + + @app_commands.command() + @app_commands.describe(user='User whose profile you would like to view') + async def profile( + self, + interaction: discord.Interaction, + user: discord.Member + ): + "Show the profile for the given user" + user_id = user.id if user else interaction.user.id + profile = await self.economy.get_entry(user_id) + balance = profile[1] + + cur = CONNECTION.cursor() + cur.execute("SELECT rank_name FROM profile WHERE user_id = %s ORDER BY rank_int DESC", (user_id,)) + data = cur.fetchall() + if data: + names = ', '.join([str(i[0]) for i in data]) + + else: + names = "No ranks" + + embed = discord.Embed( + title=f"Profile For - {await self.bot.fetch_user(user_id)}", + description=f"Below will show all economy information for this user", + color=BOT_COLOR + ) + + embed.add_field(name="Money Balance:", value=f"${balance:,}", inline=False) + embed.add_field(name="Ranks:", value=f"{names}", inline=False) + embed.set_thumbnail(url = user.avatar.url) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.command() + async def shop( + self, + interaction: discord.Interaction + ): + "Shows the shop so that you can buy items" + user_id = interaction.user.id + profile = await self.economy.get_entry(user_id) + balance = profile[1] + + cur = CONNECTION.cursor() + cur.execute("SELECT rank_name FROM profile WHERE user_id = %s ORDER BY rank_int DESC", (user_id,)) + data = cur.fetchall() + if data: + names = ', '.join([str(i[0]) for i in data]) + + else: + names = "No ranks" + + embed = discord.Embed( + title="Shop", + description=f"Choose from one of the categories below in order to shop for items \n\nBalance: **${balance:,}** \n\nRanks: **{names}**", + color=BOT_COLOR + ) + view = ShopView(self.bot) + await interaction.response.send_message(embed=embed, view=view, ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Profile(bot))
\ No newline at end of file diff --git a/code/cogs/slots.py b/code/cogs/slots.py new file mode 100644 index 0000000..466ccb7 --- /dev/null +++ b/code/cogs/slots.py @@ -0,0 +1,81 @@ +import os +import random +import datetime +import discord +from discord.ext import commands +from discord import app_commands + +from bot import InsufficientFundsException +from database import Database + + +"""NOTE: The code to create the slot reels was found on GitHub a while ago, same person who +made the blackjack stuff (still don't know who it was). I ended up modifying it quite a lot with my own images, and changed +the code a bit so that I could have it generate the slot reels and put them in the correct folders + +I premade the slot reels as it uses less resources to just load them from the folder than to +generate them every time someone uses the command.""" + +class Slots(commands.Cog): + def __init__(self, bot: commands.Bot): + self.bot = bot + self.economy = Database(bot) + + async def check_bet( + self, + interaction: discord.Interaction, + bet + ): + bet = int(bet) + if bet <= 0: + raise commands.errors.BadArgument() + current = (await self.economy.get_entry(interaction.user.id))[1] + if bet > current: + raise InsufficientFundsException() + + + @app_commands.command() + @app_commands.checks.cooldown(1, 4) + @app_commands.describe(bet='Amount of money to bet') + async def slots( + self, + interaction: discord.Interaction, + bet: app_commands.Range[int, 1, None] + ): + "Bet a specified amount of money on the slot machines" + await self.check_bet(interaction, bet) + + win_rate = 5/100 + multiplier_dict = {"seven": 80, "diamond": 40, "bar": 25, "clover": 10, "grape": 5, "lemon": 4} + + if random.random() < win_rate: + # Pick a random word + word = random.choice(["seven", "diamond", "bar", "clover", "grape", "lemon"]) + multiplier = multiplier_dict[word] + # Pick one of the 10 images of the winning reel type (seven, diamond, etc.) + image_path = f"code/utils/winning_reels/{word}_{random.randint(1, 10)}.gif" + amount = bet * multiplier + + else: + # Pick a random number 1-5, this will decide which losing folder we pick a reel from + folder = f"code/utils/losing_reels_{random.randint(1, 5)}" + # Pick a random image from the folder + image_path = f"{folder}/{random.choice(os.listdir(folder))}" + amount = bet * -1 + + await self.economy.add_money(interaction.user.id, amount) + current = (await self.economy.get_entry(interaction.user.id))[1] + + file = discord.File(image_path, "slot_machine.gif") + embed = discord.Embed( + title=f"You {'won' if amount > 0 else 'lost'} {abs(amount):,} {'dollar' if abs(amount) == 1 else 'dollars'}!", + description=f"You now have {current:,} {'dollar' if current == 1 else 'dollars'}", + color=discord.Color.green() if amount > 0 else discord.Color.red() + ) + embed.set_image(url="attachment://slot_machine.gif") + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, file=file) + + +async def setup(bot: commands.Bot): + await bot.add_cog(Slots(bot))
\ No newline at end of file diff --git a/code/cogs/socketfix.py b/code/cogs/socketfix.py new file mode 100644 index 0000000..9dfbf29 --- /dev/null +++ b/code/cogs/socketfix.py @@ -0,0 +1,35 @@ +import zlib +import discord + +from discord.ext import commands + +class SocketFix(commands.Cog): + def __init__(self, bot): + self.bot = bot + + self._zlib = zlib.decompressobj() + self._buffer = bytearray() + + @commands.Cog.listener() + async def on_socket_raw_receive(self, msg): + if type(msg) is bytes: + self._buffer.extend(msg) + + if len(msg) < 4 or msg[-4:] != b'\x00\x00\xff\xff': + return + + try: + msg = self._zlib.decompress(self._buffer) + except Exception: + self._buffer = bytearray() # Reset buffer on fail just in case... + return + + msg = msg.decode('utf-8') + self._buffer = bytearray() + + msg = discord.utils._from_json(msg) + self.bot.dispatch('on_socket_response', msg) + + +async def setup(bot): + await bot.add_cog(SocketFix(bot))
\ No newline at end of file diff --git a/code/cogs/tree_sync.py b/code/cogs/tree_sync.py new file mode 100644 index 0000000..d3f8307 --- /dev/null +++ b/code/cogs/tree_sync.py @@ -0,0 +1,33 @@ +from discord.ext import commands +from discord import Object + +class TreeSync(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @commands.command() + @commands.dm_only() + @commands.is_owner() + async def sync(self, ctx: commands.Context, *, guild: Object=None) -> None: + if not guild or guild == None: + await self.bot.tree.sync() + await ctx.author.send("Synced commands globally") + return + + elif guild != None: + self.bot.tree.copy_global_to(guild=guild) + await self.bot.tree.sync(guild=guild) + + await ctx.author.send(f"Synced the tree to 1 test guild.") + + @sync.error + async def error_sync(self, ctx, error): + if isinstance(error, commands.errors.PrivateMessageOnly): + pass + else: + await ctx.author.send("That is not a valid guild ID") + + +async def setup(bot): + await bot.add_cog(TreeSync(bot))
\ No newline at end of file diff --git a/code/cogs/warnings.py b/code/cogs/warnings.py new file mode 100644 index 0000000..f9d84bc --- /dev/null +++ b/code/cogs/warnings.py @@ -0,0 +1,121 @@ +import discord +from discord.ext import commands +import datetime +from discord import app_commands + +from global_variables import CONNECTION, BOT_COLOR + + +class Warnings(commands.Cog): + def __init__(self, bot): + self.bot = bot + + + @app_commands.default_permissions(manage_messages=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_messages=True) + @app_commands.describe(member='The member you would like to warn') + @app_commands.describe(reason='Reason for warning the member') + async def warn( + self, + interaction: discord.Interaction, + member: discord.Member, + reason: str + ): + "Warn a member for a given reason" + guild_id = interaction.user.guild.id + user_id = member.id + warn_id = interaction.id + warn_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + warned_by = interaction.user.id + + cur = CONNECTION.cursor() + cur.execute("INSERT INTO warnings (warn_id, guild_id, user_id, warning, warn_time, warned_by) VALUES (%s, %s, %s, %s, %s, %s)", (warn_id, guild_id, user_id, reason, warn_time, warned_by)) + CONNECTION.commit() + + embed = discord.Embed( + title=f"`{member.name}#{member.discriminator}` Has Been Warned in {interaction.guild}", + description=f"Reason: {reason}", + color=discord.Color.orange() + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.default_permissions(manage_messages=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_messages=True) + @app_commands.describe(member='The member whose warnings you want to see') + async def warnings( + self, + interaction: discord.Interaction, + member: discord.Member + ): + "Show all warnings for a given user" + guild_id = interaction.user.guild.id + user_id = member.id + + cur = CONNECTION.cursor() + cur.execute("SELECT * FROM warnings WHERE guild_id = %s AND user_id = %s", (guild_id, user_id)) + data = cur.fetchall() + + if data: + embed=discord.Embed( + title=f"Warnings for `{member.name}#{member.discriminator}` in {interaction.guild}", + description="", + color=discord.Color.orange() + ) + for entry in data: + embed.description += f"**Reason - `\"{entry[3]}\"` | ID: `{entry[0]}`**\nWarned By: <@{entry[5]}> | Date: {entry[4]}\n\n" + + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + else: + embed = discord.Embed( + title="No Warnings", + description=f"{member.mention} has not been warned in the past, or all of their warnings have been deleted.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + + @app_commands.default_permissions(manage_messages=True) + @app_commands.command() + @app_commands.checks.has_permissions(manage_messages=True) + @app_commands.describe(id='ID of the warning you would like to delete') + async def delwarn( + self, + interaction: discord.Interaction, + id: str + ): + "Delete a warning from a user with the warning ID" + cur = CONNECTION.cursor() + cur.execute("SELECT warn_id FROM warnings WHERE warn_id = %s", (id,)) + data = cur.fetchone() + + if data: + cur.execute("DELETE FROM warnings WHERE warn_id = %s", (id,)) + CONNECTION.commit() + + embed = discord.Embed( + title="Warning Deleted", + description=f"{interaction.user.mention} has deleted the warning identified by `{id}` .", + color=discord.Color.orange() + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed) + + else: + embed = discord.Embed( + title="Invalid ID", + description="That ID is not associated with any warnings in this server.", + color=BOT_COLOR + ) + embed.set_footer(text=datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%d %H:%M:%S')+" UTC") + await interaction.response.send_message(embed=embed, ephemeral=True) + + +async def setup(bot): + await bot.add_cog(Warnings(bot))
\ No newline at end of file diff --git a/code/count/PLACEHOLDER b/code/count/PLACEHOLDER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/code/count/PLACEHOLDER diff --git a/code/custom_source.py b/code/custom_source.py new file mode 100644 index 0000000..cf1510e --- /dev/null +++ b/code/custom_source.py @@ -0,0 +1,74 @@ +from lavalink import LoadResult, LoadType, Source, DeferredAudioTrack, PlaylistInfo + + +class LoadError(Exception): # We'll raise this if we have trouble loading our track. + pass + + +class CustomAudioTrack(DeferredAudioTrack): + # A DeferredAudioTrack allows us to load metadata now, and a playback URL later. + # This makes the DeferredAudioTrack highly efficient, particularly in cases + # where large playlists are loaded. + + async def load(self, client): # Load our 'actual' playback track using the metadata from this one. + result: LoadResult = await client.get_tracks('ytsearch:{0.title} {0.author} audio'.format(self)) # Search for our track on YouTube. + + if result.load_type != LoadType.SEARCH or not result.tracks: # We're expecting a 'SEARCH' due to our 'ytsearch' prefix above. + raise LoadError + + first_track = result.tracks[0] # Grab the first track from the results. + base64 = first_track.track # Extract the base64 string from the track. + self.track = base64 # We'll store this for later, as it allows us to save making network requests + # if this track is re-used (e.g. repeat). + + return base64 + + +class CustomSource(Source): + def __init__(self): + super().__init__(name='custom') # Initialising our custom source with the name 'custom'. + + async def load_item(self, user, metadata): + track = CustomAudioTrack({ # Create an instance of our CustomAudioTrack. + 'identifier': metadata['id'], # Fill it with metadata that we've obtained from our source's provider. + 'isSeekable': True, + 'author': metadata['artists'][0]['name'], + 'length': metadata['duration_ms'], + 'isStream': False, + 'title': metadata['name'], + 'uri': metadata['external_urls']['spotify'], + 'duration': metadata['duration_ms'], + }, requester=user, extra=metadata['album']['images'][0]['url']) + return LoadResult(LoadType.TRACK, [track], playlist_info=PlaylistInfo.none()) + + async def load_album(self, user, metadata): + tracks = [] + for track in metadata['tracks']['items']: # Loop through each track in the album. + tracks.append(CustomAudioTrack({ # Create an instance of our CustomAudioTrack. + 'identifier': track['id'], # Fill it with metadata that we've obtained from our source's provider. + 'isSeekable': True, + 'author': track['artists'][0]['name'], + 'length': track['duration_ms'], + 'isStream': False, + 'title': track['name'], + 'uri': track['external_urls']['spotify'], + 'duration': track['duration_ms'], + }, requester=user, extra=metadata['images'][0]['url'])) + + return LoadResult(LoadType.PLAYLIST, tracks, playlist_info=PlaylistInfo.none()) + + async def load_playlist(self, user, metadata): + tracks = [] + for track in metadata['tracks']['items']: # Loop through each track in the playlist. + tracks.append(CustomAudioTrack({ # Create an instance of our CustomAudioTrack. + 'identifier': track['track']['id'], # Fill it with metadata that we've obtained from our source's provider. + 'isSeekable': True, + 'author': track['track']['artists'][0]['name'], + 'length': track['track']['duration_ms'], + 'isStream': False, + 'title': track['track']['name'], + 'uri': track['track']['external_urls']['spotify'], + 'duration': track['track']['duration_ms'], + }, requster=user, extra=track['track']['album']['images'][0]['url'])) + + return LoadResult(LoadType.PLAYLIST, tracks, playlist_info=PlaylistInfo.none())
\ No newline at end of file diff --git a/code/database.py b/code/database.py new file mode 100644 index 0000000..34bc954 --- /dev/null +++ b/code/database.py @@ -0,0 +1,74 @@ +import aiosqlite +import random +from typing import Tuple, List + +from global_variables import CONNECTION + + +async def init_database(): + cur = CONNECTION.cursor() + cur.execute("CREATE TABLE IF NOT EXISTS guildData (guild_id BIGINT, user_id BIGINT, exp BIGINT, PRIMARY KEY (guild_id, user_id))") + cur.execute("CREATE TABLE IF NOT EXISTS mute (guild_id BIGINT, role_id BIGINT, PRIMARY KEY (guild_id, role_id))") + cur.execute("CREATE TABLE IF NOT EXISTS tempmute (guild_id BIGINT, user_id BIGINT, role_id BIGINT, time TIMESTAMP, PRIMARY KEY (guild_id, user_id))") + cur.execute("CREATE TABLE IF NOT EXISTS warnings (warn_id BIGINT, guild_id BIGINT, user_id BIGINT, warning TEXT, warn_time DATE, warned_by BIGINT, PRIMARY KEY (warn_id))") + cur.execute("CREATE TABLE IF NOT EXISTS economy (user_id BIGINT NOT NULL PRIMARY KEY, money BIGINT NOT NULL DEFAULT 0)") + cur.execute("CREATE TABLE IF NOT EXISTS profile (user_id BIGINT, rank_name TEXT, rank_int BIGINT, UNIQUE (user_id, rank_name, rank_int))") + CONNECTION.commit() + + cur = await aiosqlite.connect("code/count/count.db") + await cur.execute("CREATE TABLE IF NOT EXISTS count (count INTEGER)") + await cur.commit() + await cur.close() + + +Entry = Tuple[int, int] + +class Database: + def __init__(self, bot): + self.bot = bot + + async def get_entry(self, user_id: int) -> Entry: + cur = CONNECTION.cursor() + cur.execute("SELECT * FROM economy WHERE user_id = %s", (user_id,)) + result = cur.fetchone() + if result: + return result + return await self.new_entry(user_id) + + async def new_entry(self, user_id: int) -> Entry: + try: + cur = CONNECTION.cursor() + cur.execute("INSERT INTO economy(user_id, money) VALUES(%s, %s)", (user_id, 0)) + CONNECTION.commit() + return await self.get_entry(user_id) + except: + return await self.get_entry(user_id) + + async def remove_entry(self, user_id: int) -> None: + cur = CONNECTION.cursor() + cur.execute("DELETE FROM economy WHERE user_id = %s", (user_id,)) + CONNECTION.commit() + + async def set_money(self, user_id: int, money: int) -> Entry: + cur = CONNECTION.cursor() + cur.execute("UPDATE economy SET money = %s WHERE user_id = %s", (money, user_id)) + CONNECTION.commit() + return await self.get_entry(user_id) + + async def add_money(self, user_id: int, money_to_add: int) -> Entry: + money = (await self.get_entry(user_id))[1] + total = money + money_to_add + if total < 0: + total = 0 + await self.set_money(user_id, total) + return await self.get_entry(user_id) + + async def random_entry(self) -> Entry: + cur = CONNECTION.cursor() + cur.execute("SELECT * FROM economy") + return random.choice(cur.fetchall()) + + async def top_entries(self, n: int=0) -> List[Entry]: + cur = CONNECTION.cursor() + cur.execute("SELECT * FROM economy ORDER BY money DESC") + return cur.fetchmany(n) if n else cur.fetchall()
\ No newline at end of file diff --git a/code/global_variables.py b/code/global_variables.py new file mode 100644 index 0000000..86b1814 --- /dev/null +++ b/code/global_variables.py @@ -0,0 +1,49 @@ +import configparser +import logging +from colorlog import ColoredFormatter +import psycopg2 +import discord + +log_level = logging.DEBUG +log_format = " %(log_color)s%(levelname)-8s%(reset)s | %(log_color)s%(message)s%(reset)s" + +logging.root.setLevel(log_level) +formatter = ColoredFormatter(log_format) + +stream = logging.StreamHandler() +stream.setLevel(log_level) +stream.setFormatter(formatter) + +LOG = logging.getLogger('pythonConfig') +LOG.setLevel(log_level) +LOG.addHandler(stream) + + +with open('config.ini', 'r')as f: + file_contents = f.read() + +config = configparser.ConfigParser() +config.read_string(file_contents) + +BOT_TOKEN = config['BOT_INFO']['TOKEN'] +BONUS_COOLDOWN = int(config['BOT_INFO']['BONUS_COOLDOWN']) +BOT_COLOR = discord.Color(int((config['BOT_INFO']['BOT_COLOR']).replace('#', ""), 16)) +BUG_CHANNEL_ID = int(config['BOT_INFO']['BUG_CHANNEL_ID']) +FEEDBACK_CHANNEL_ID = int(config['BOT_INFO']['FEEDBACK_CHANNEL_ID']) + +CRYPTO_COMPARE_API_KEY = config['CRYPTO_COMPARE']['API_KEY'] + +username = config['POSTGRESQL']['USERNAME'] +password = config['POSTGRESQL']['PASSWORD'] +host = config['POSTGRESQL']['HOST'] +port = config['POSTGRESQL']['PORT'] +database = config['POSTGRESQL']['DATABASE'] +connection_string = f"postgresql://{username}:{password}@{host}:{port}/{database}" +CONNECTION = psycopg2.connect(connection_string) + +LAVALINK_HOST = config['LAVALINK']['HOST'] +LAVALINK_PORT = config['LAVALINK']['PORT'] +LAVALINK_PASSWORD = config['LAVALINK']['PASSWORD'] + +SPOTIFY_CLIENT_ID = config['SPOTIFY']['CLIENT_ID'] +SPOTIFY_CLIENT_SECRET = config['SPOTIFY']['CLIENT_SECRET']
\ No newline at end of file diff --git a/code/players/reels/PLACEHOLDER b/code/players/reels/PLACEHOLDER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/code/players/reels/PLACEHOLDER diff --git a/code/players/tables/PLACEHOLDER b/code/players/tables/PLACEHOLDER new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/code/players/tables/PLACEHOLDER diff --git a/code/utils/AquaBot.png b/code/utils/AquaBot.png Binary files differnew file mode 100644 index 0000000..e2d7ec5 --- /dev/null +++ b/code/utils/AquaBot.png diff --git a/code/utils/cards/10C.png b/code/utils/cards/10C.png Binary files differnew file mode 100644 index 0000000..a480340 --- /dev/null +++ b/code/utils/cards/10C.png diff --git a/code/utils/cards/10D.png b/code/utils/cards/10D.png Binary files differnew file mode 100644 index 0000000..e00abc5 --- /dev/null +++ b/code/utils/cards/10D.png diff --git a/code/utils/cards/10H.png b/code/utils/cards/10H.png Binary files differnew file mode 100644 index 0000000..3e95b05 --- /dev/null +++ b/code/utils/cards/10H.png diff --git a/code/utils/cards/10S.png b/code/utils/cards/10S.png Binary files differnew file mode 100644 index 0000000..52338a8 --- /dev/null +++ b/code/utils/cards/10S.png diff --git a/code/utils/cards/2C.png b/code/utils/cards/2C.png Binary files differnew file mode 100644 index 0000000..8a76d6e --- /dev/null +++ b/code/utils/cards/2C.png diff --git a/code/utils/cards/2D.png b/code/utils/cards/2D.png Binary files differnew file mode 100644 index 0000000..3ae35fc --- /dev/null +++ b/code/utils/cards/2D.png diff --git a/code/utils/cards/2H.png b/code/utils/cards/2H.png Binary files differnew file mode 100644 index 0000000..9f7debe --- /dev/null +++ b/code/utils/cards/2H.png diff --git a/code/utils/cards/2S.png b/code/utils/cards/2S.png Binary files differnew file mode 100644 index 0000000..353fe87 --- /dev/null +++ b/code/utils/cards/2S.png diff --git a/code/utils/cards/3C.png b/code/utils/cards/3C.png Binary files differnew file mode 100644 index 0000000..7791836 --- /dev/null +++ b/code/utils/cards/3C.png diff --git a/code/utils/cards/3D.png b/code/utils/cards/3D.png Binary files differnew file mode 100644 index 0000000..f3d7abe --- /dev/null +++ b/code/utils/cards/3D.png diff --git a/code/utils/cards/3H.png b/code/utils/cards/3H.png Binary files differnew file mode 100644 index 0000000..89aebaa --- /dev/null +++ b/code/utils/cards/3H.png diff --git a/code/utils/cards/3S.png b/code/utils/cards/3S.png Binary files differnew file mode 100644 index 0000000..3109650 --- /dev/null +++ b/code/utils/cards/3S.png diff --git a/code/utils/cards/4C.png b/code/utils/cards/4C.png Binary files differnew file mode 100644 index 0000000..1319a50 --- /dev/null +++ b/code/utils/cards/4C.png diff --git a/code/utils/cards/4D.png b/code/utils/cards/4D.png Binary files differnew file mode 100644 index 0000000..5539d75 --- /dev/null +++ b/code/utils/cards/4D.png diff --git a/code/utils/cards/4H.png b/code/utils/cards/4H.png Binary files differnew file mode 100644 index 0000000..8426b8f --- /dev/null +++ b/code/utils/cards/4H.png diff --git a/code/utils/cards/4S.png b/code/utils/cards/4S.png Binary files differnew file mode 100644 index 0000000..043afa5 --- /dev/null +++ b/code/utils/cards/4S.png diff --git a/code/utils/cards/5C.png b/code/utils/cards/5C.png Binary files differnew file mode 100644 index 0000000..301aa7b --- /dev/null +++ b/code/utils/cards/5C.png diff --git a/code/utils/cards/5D.png b/code/utils/cards/5D.png Binary files differnew file mode 100644 index 0000000..fe095b2 --- /dev/null +++ b/code/utils/cards/5D.png diff --git a/code/utils/cards/5H.png b/code/utils/cards/5H.png Binary files differnew file mode 100644 index 0000000..a94db67 --- /dev/null +++ b/code/utils/cards/5H.png diff --git a/code/utils/cards/5S.png b/code/utils/cards/5S.png Binary files differnew file mode 100644 index 0000000..c03b24b --- /dev/null +++ b/code/utils/cards/5S.png diff --git a/code/utils/cards/6C.png b/code/utils/cards/6C.png Binary files differnew file mode 100644 index 0000000..17358c6 --- /dev/null +++ b/code/utils/cards/6C.png diff --git a/code/utils/cards/6D.png b/code/utils/cards/6D.png Binary files differnew file mode 100644 index 0000000..fd457d6 --- /dev/null +++ b/code/utils/cards/6D.png diff --git a/code/utils/cards/6H.png b/code/utils/cards/6H.png Binary files differnew file mode 100644 index 0000000..efcb385 --- /dev/null +++ b/code/utils/cards/6H.png diff --git a/code/utils/cards/6S.png b/code/utils/cards/6S.png Binary files differnew file mode 100644 index 0000000..899adf5 --- /dev/null +++ b/code/utils/cards/6S.png diff --git a/code/utils/cards/7C.png b/code/utils/cards/7C.png Binary files differnew file mode 100644 index 0000000..5f30e60 --- /dev/null +++ b/code/utils/cards/7C.png diff --git a/code/utils/cards/7D.png b/code/utils/cards/7D.png Binary files differnew file mode 100644 index 0000000..b211f14 --- /dev/null +++ b/code/utils/cards/7D.png diff --git a/code/utils/cards/7H.png b/code/utils/cards/7H.png Binary files differnew file mode 100644 index 0000000..b1f51f8 --- /dev/null +++ b/code/utils/cards/7H.png diff --git a/code/utils/cards/7S.png b/code/utils/cards/7S.png Binary files differnew file mode 100644 index 0000000..66b02d0 --- /dev/null +++ b/code/utils/cards/7S.png diff --git a/code/utils/cards/8C.png b/code/utils/cards/8C.png Binary files differnew file mode 100644 index 0000000..401dd4b --- /dev/null +++ b/code/utils/cards/8C.png diff --git a/code/utils/cards/8D.png b/code/utils/cards/8D.png Binary files differnew file mode 100644 index 0000000..f1a50e4 --- /dev/null +++ b/code/utils/cards/8D.png diff --git a/code/utils/cards/8H.png b/code/utils/cards/8H.png Binary files differnew file mode 100644 index 0000000..e977bc6 --- /dev/null +++ b/code/utils/cards/8H.png diff --git a/code/utils/cards/8S.png b/code/utils/cards/8S.png Binary files differnew file mode 100644 index 0000000..0124ce1 --- /dev/null +++ b/code/utils/cards/8S.png diff --git a/code/utils/cards/9C.png b/code/utils/cards/9C.png Binary files differnew file mode 100644 index 0000000..73cb646 --- /dev/null +++ b/code/utils/cards/9C.png diff --git a/code/utils/cards/9D.png b/code/utils/cards/9D.png Binary files differnew file mode 100644 index 0000000..7e2a7ef --- /dev/null +++ b/code/utils/cards/9D.png diff --git a/code/utils/cards/9H.png b/code/utils/cards/9H.png Binary files differnew file mode 100644 index 0000000..8fca4ab --- /dev/null +++ b/code/utils/cards/9H.png diff --git a/code/utils/cards/9S.png b/code/utils/cards/9S.png Binary files differnew file mode 100644 index 0000000..5eb8b9f --- /dev/null +++ b/code/utils/cards/9S.png diff --git a/code/utils/cards/AC.png b/code/utils/cards/AC.png Binary files differnew file mode 100644 index 0000000..ec42917 --- /dev/null +++ b/code/utils/cards/AC.png diff --git a/code/utils/cards/AD.png b/code/utils/cards/AD.png Binary files differnew file mode 100644 index 0000000..c755b96 --- /dev/null +++ b/code/utils/cards/AD.png diff --git a/code/utils/cards/AH.png b/code/utils/cards/AH.png Binary files differnew file mode 100644 index 0000000..9ec396c --- /dev/null +++ b/code/utils/cards/AH.png diff --git a/code/utils/cards/AS.png b/code/utils/cards/AS.png Binary files differnew file mode 100644 index 0000000..27242e5 --- /dev/null +++ b/code/utils/cards/AS.png diff --git a/code/utils/cards/JC.png b/code/utils/cards/JC.png Binary files differnew file mode 100644 index 0000000..b74c53c --- /dev/null +++ b/code/utils/cards/JC.png diff --git a/code/utils/cards/JD.png b/code/utils/cards/JD.png Binary files differnew file mode 100644 index 0000000..953e289 --- /dev/null +++ b/code/utils/cards/JD.png diff --git a/code/utils/cards/JH.png b/code/utils/cards/JH.png Binary files differnew file mode 100644 index 0000000..14d0198 --- /dev/null +++ b/code/utils/cards/JH.png diff --git a/code/utils/cards/JS.png b/code/utils/cards/JS.png Binary files differnew file mode 100644 index 0000000..c4cae81 --- /dev/null +++ b/code/utils/cards/JS.png diff --git a/code/utils/cards/KC.png b/code/utils/cards/KC.png Binary files differnew file mode 100644 index 0000000..e884b31 --- /dev/null +++ b/code/utils/cards/KC.png diff --git a/code/utils/cards/KD.png b/code/utils/cards/KD.png Binary files differnew file mode 100644 index 0000000..fece39a --- /dev/null +++ b/code/utils/cards/KD.png diff --git a/code/utils/cards/KH.png b/code/utils/cards/KH.png Binary files differnew file mode 100644 index 0000000..0c857f8 --- /dev/null +++ b/code/utils/cards/KH.png diff --git a/code/utils/cards/KS.png b/code/utils/cards/KS.png Binary files differnew file mode 100644 index 0000000..505b3b4 --- /dev/null +++ b/code/utils/cards/KS.png diff --git a/code/utils/cards/QC.png b/code/utils/cards/QC.png Binary files differnew file mode 100644 index 0000000..00143d3 --- /dev/null +++ b/code/utils/cards/QC.png diff --git a/code/utils/cards/QD.png b/code/utils/cards/QD.png Binary files differnew file mode 100644 index 0000000..f28c02b --- /dev/null +++ b/code/utils/cards/QD.png diff --git a/code/utils/cards/QH.png b/code/utils/cards/QH.png Binary files differnew file mode 100644 index 0000000..6cafe44 --- /dev/null +++ b/code/utils/cards/QH.png diff --git a/code/utils/cards/QS.png b/code/utils/cards/QS.png Binary files differnew file mode 100644 index 0000000..3955655 --- /dev/null +++ b/code/utils/cards/QS.png diff --git a/code/utils/cards/aces.png b/code/utils/cards/aces.png Binary files differnew file mode 100644 index 0000000..58d0e46 --- /dev/null +++ b/code/utils/cards/aces.png diff --git a/code/utils/cards/red_back.png b/code/utils/cards/red_back.png Binary files differnew file mode 100644 index 0000000..1164d5c --- /dev/null +++ b/code/utils/cards/red_back.png diff --git a/code/utils/covid.png b/code/utils/covid.png Binary files differnew file mode 100644 index 0000000..9e303b9 --- /dev/null +++ b/code/utils/covid.png diff --git a/code/utils/crypto_icons/algorand.png b/code/utils/crypto_icons/algorand.png Binary files differnew file mode 100644 index 0000000..4154259 --- /dev/null +++ b/code/utils/crypto_icons/algorand.png diff --git a/code/utils/crypto_icons/avalanche.png b/code/utils/crypto_icons/avalanche.png Binary files differnew file mode 100644 index 0000000..6b11e04 --- /dev/null +++ b/code/utils/crypto_icons/avalanche.png diff --git a/code/utils/crypto_icons/binance.png b/code/utils/crypto_icons/binance.png Binary files differnew file mode 100644 index 0000000..8442fdc --- /dev/null +++ b/code/utils/crypto_icons/binance.png diff --git a/code/utils/crypto_icons/bitcoin.png b/code/utils/crypto_icons/bitcoin.png Binary files differnew file mode 100644 index 0000000..88838e2 --- /dev/null +++ b/code/utils/crypto_icons/bitcoin.png diff --git a/code/utils/crypto_icons/bitcoin_cash.png b/code/utils/crypto_icons/bitcoin_cash.png Binary files differnew file mode 100644 index 0000000..9620240 --- /dev/null +++ b/code/utils/crypto_icons/bitcoin_cash.png diff --git a/code/utils/crypto_icons/cardano.png b/code/utils/crypto_icons/cardano.png Binary files differnew file mode 100644 index 0000000..5b03c5a --- /dev/null +++ b/code/utils/crypto_icons/cardano.png diff --git a/code/utils/crypto_icons/chainlink.png b/code/utils/crypto_icons/chainlink.png Binary files differnew file mode 100644 index 0000000..52cc730 --- /dev/null +++ b/code/utils/crypto_icons/chainlink.png diff --git a/code/utils/crypto_icons/dogecoin.png b/code/utils/crypto_icons/dogecoin.png Binary files differnew file mode 100644 index 0000000..3cb65ef --- /dev/null +++ b/code/utils/crypto_icons/dogecoin.png diff --git a/code/utils/crypto_icons/ethereum.png b/code/utils/crypto_icons/ethereum.png Binary files differnew file mode 100644 index 0000000..a211ef1 --- /dev/null +++ b/code/utils/crypto_icons/ethereum.png diff --git a/code/utils/crypto_icons/internet_computer.png b/code/utils/crypto_icons/internet_computer.png Binary files differnew file mode 100644 index 0000000..5ed648a --- /dev/null +++ b/code/utils/crypto_icons/internet_computer.png diff --git a/code/utils/crypto_icons/litecoin.png b/code/utils/crypto_icons/litecoin.png Binary files differnew file mode 100644 index 0000000..0c51ef3 --- /dev/null +++ b/code/utils/crypto_icons/litecoin.png diff --git a/code/utils/crypto_icons/polkadot.png b/code/utils/crypto_icons/polkadot.png Binary files differnew file mode 100644 index 0000000..e464738 --- /dev/null +++ b/code/utils/crypto_icons/polkadot.png diff --git a/code/utils/crypto_icons/polygon.png b/code/utils/crypto_icons/polygon.png Binary files differnew file mode 100644 index 0000000..a945ba0 --- /dev/null +++ b/code/utils/crypto_icons/polygon.png diff --git a/code/utils/crypto_icons/shiba.png b/code/utils/crypto_icons/shiba.png Binary files differnew file mode 100644 index 0000000..acbdcf6 --- /dev/null +++ b/code/utils/crypto_icons/shiba.png diff --git a/code/utils/crypto_icons/solana.png b/code/utils/crypto_icons/solana.png Binary files differnew file mode 100644 index 0000000..1cdc1e3 --- /dev/null +++ b/code/utils/crypto_icons/solana.png diff --git a/code/utils/crypto_icons/stellar.png b/code/utils/crypto_icons/stellar.png Binary files differnew file mode 100644 index 0000000..e73ee2f --- /dev/null +++ b/code/utils/crypto_icons/stellar.png diff --git a/code/utils/crypto_icons/terra.png b/code/utils/crypto_icons/terra.png Binary files differnew file mode 100644 index 0000000..043537b --- /dev/null +++ b/code/utils/crypto_icons/terra.png diff --git a/code/utils/crypto_icons/uniswap.png b/code/utils/crypto_icons/uniswap.png Binary files differnew file mode 100644 index 0000000..94999f6 --- /dev/null +++ b/code/utils/crypto_icons/uniswap.png diff --git a/code/utils/crypto_icons/vechain.png b/code/utils/crypto_icons/vechain.png Binary files differnew file mode 100644 index 0000000..d3bd98e --- /dev/null +++ b/code/utils/crypto_icons/vechain.png diff --git a/code/utils/crypto_icons/xrp.png b/code/utils/crypto_icons/xrp.png Binary files differnew file mode 100644 index 0000000..1f7bfb2 --- /dev/null +++ b/code/utils/crypto_icons/xrp.png diff --git a/code/utils/losing_reels_1.zip b/code/utils/losing_reels_1.zip Binary files differnew file mode 100644 index 0000000..de2e493 --- /dev/null +++ b/code/utils/losing_reels_1.zip diff --git a/code/utils/losing_reels_2.zip b/code/utils/losing_reels_2.zip Binary files differnew file mode 100644 index 0000000..cab681f --- /dev/null +++ b/code/utils/losing_reels_2.zip diff --git a/code/utils/losing_reels_3.zip b/code/utils/losing_reels_3.zip Binary files differnew file mode 100644 index 0000000..b8c179c --- /dev/null +++ b/code/utils/losing_reels_3.zip diff --git a/code/utils/losing_reels_4.zip b/code/utils/losing_reels_4.zip Binary files differnew file mode 100644 index 0000000..d9d7b3f --- /dev/null +++ b/code/utils/losing_reels_4.zip diff --git a/code/utils/losing_reels_5.zip b/code/utils/losing_reels_5.zip Binary files differnew file mode 100644 index 0000000..b99b9e2 --- /dev/null +++ b/code/utils/losing_reels_5.zip diff --git a/code/utils/slot-face.png b/code/utils/slot-face.png Binary files differnew file mode 100644 index 0000000..cb4deda --- /dev/null +++ b/code/utils/slot-face.png diff --git a/code/utils/slot-reel.png b/code/utils/slot-reel.png Binary files differnew file mode 100644 index 0000000..dee0ab3 --- /dev/null +++ b/code/utils/slot-reel.png diff --git a/code/utils/table.png b/code/utils/table.png Binary files differnew file mode 100644 index 0000000..72c3f47 --- /dev/null +++ b/code/utils/table.png diff --git a/code/utils/winning_reels.zip b/code/utils/winning_reels.zip Binary files differnew file mode 100644 index 0000000..b508e96 --- /dev/null +++ b/code/utils/winning_reels.zip diff --git a/code/validate_config.py b/code/validate_config.py new file mode 100644 index 0000000..ffcdb76 --- /dev/null +++ b/code/validate_config.py @@ -0,0 +1,175 @@ +import configparser +import re + +from global_variables import LOG + + +pattern_1 = "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" +pattern_2 = "^([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" + +def validate_config(file_contents): + config = configparser.ConfigParser() + config.read_string(file_contents) + + errors = 0 + + try: + # Validate TOKEN + if not config['BOT_INFO']['TOKEN']: + LOG.critical("TOKEN has not been set.") + errors += 1 + # Validate BONUS_COOLDOWN + if not config['BOT_INFO']['BONUS_COOLDOWN']: + LOG.critical("BONUS_COOLDOWN has not been set.") + errors += 1 + + else: + try: + int(config['BOT_INFO']['BONUS_COOLDOWN']) + except ValueError: + LOG.critical("BONUS_COOLDOWN must be an integer value.") + errors += 1 + # Validate BOT_COLOR + if not config['BOT_INFO']['BOT_COLOR']: + LOG.critical("BOT_COLOR has not been set.") + errors += 1 + + elif not bool(re.match(pattern_1, config['BOT_INFO']['BOT_COLOR'])) and not bool(re.match(pattern_2, config['BOT_INFO']['BOT_COLOR'])): + LOG.critical("BOT_COLOR is not a valid hex color.") + errors += 1 + # Validate BUG_CHANNEL_ID + if not config['BOT_INFO']['BUG_CHANNEL_ID']: + LOG.critical("BUG_CHANNEL_ID has not been set.") + errors += 1 + + elif len(str(config['BOT_INFO']['BUG_CHANNEL_ID'])) != 19: + LOG.critical("BUG_CHANNEL_ID is not a valid Discord text channel ID.") + errors += 1 + + else: + try: + int(config['BOT_INFO']['BUG_CHANNEL_ID']) + except ValueError: + LOG.critical("BUG_CHANNEL_ID should be an integer value, not a string.") + errors += 1 + # Validate FEEDBACK_CHANNEL_ID + if not config['BOT_INFO']['FEEDBACK_CHANNEL_ID']: + LOG.critical("FEEDBACK_CHANNEL_ID has not been set.") + errors += 1 + + elif len(str(config['BOT_INFO']['FEEDBACK_CHANNEL_ID'])) != 19: + LOG.critical("FEEDBACK_CHANNEL_ID is not a valid Discord text channel ID.") + errors += 1 + + else: + try: + int(config['BOT_INFO']['FEEDBACK_CHANNEL_ID']) + except ValueError: + LOG.critical("FEEDBACK_CHANNEL_ID should be an integer value, not a string.") + errors += 1 + # Validate API_KEY + if not config['CRYPTO_COMPARE']['API_KEY']: + LOG.critical("API_KEY has not been set.") + errors += 1 + # Validate USERNAME + if not config['POSTGRESQL']['USERNAME']: + LOG.critical("USERNAME has not been set.") + errors += 1 + # Validate PASSWORD + if not config['POSTGRESQL']['PASSWORD']: + LOG.critical("PASSWORD has not been set.") + errors += 1 + # Validate HOST + if not config['POSTGRESQL']['HOST']: + LOG.critical("HOST has not been set.") + errors += 1 + # Validate PORT + if not config['POSTGRESQL']['PORT']: + LOG.critical("PORT has not been set.") + errors += 1 + # Validate DATABASE + if not config['POSTGRESQL']['DATABASE']: + LOG.critical("DATABASE has not been set.") + errors += 1 + + # Validate LAVALINK + # Validate HOST + if not config['LAVALINK']['HOST']: + LOG.critical("HOST has not been set.") + errors += 1 + # Validate PORT + if not config['LAVALINK']['PORT']: + LOG.critical("PORT has not been set.") + errors += 1 + # Validate PASSWORD + if not config['LAVALINK']['PASSWORD']: + LOG.critical("HOST has not been set.") + errors += 1 + + # Validate SPOTIFY + # Validate CLIENT_ID + if not config['SPOTIFY']['CLIENT_ID']: + LOG.critical("CLIENT_ID has not been set.") + errors += 1 + # Validate CLIENT_SECRET + if not config['SPOTIFY']['CLIENT_SECRET']: + LOG.critical("CLIENT_SECRET has not been set.") + errors += 1 + + if errors > 0: + LOG.info(f"Program exiting with {errors} critical {'errors' if errors > 1 else 'error'}") + exit() + + else: + LOG.info("Configuration checks passed. Starting bot.") + + + except KeyError: + LOG.critical("You are missing at least one of the configuration options from your config.ini file. In order to regenerate this file with all of the proper options, please delete it and re-run the `bot.py` file.") + exit() + + +def create_config(): + try: + with open('config.ini', 'r') as f: + file_contents = f.read() + validate_config(file_contents) + + except FileNotFoundError: + config = configparser.ConfigParser() + config['BOT_INFO'] = { + 'TOKEN': '', + 'BONUS_COOLDOWN': '', + 'BOT_COLOR': '', + 'BUG_CHANNEL_ID': '', + 'FEEDBACK_CHANNEL_ID': '' + } + + config['CRYPTO_COMPARE'] = { + 'API_KEY': '' + } + + config['POSTGRESQL'] = { + 'USERNAME': '', + 'PASSWORD': '', + 'HOST': '', + 'PORT': '', + 'DATABASE': '' + } + + config['LAVALINK'] = { + 'HOST': '', + 'PORT': '', + 'PASSWORD': '' + } + + config['SPOTIFY'] = { + 'CLIENT_ID': '', + 'CLIENT_SECRET': '' + } + + with open('config.ini', 'w') as configfile: + config.write(configfile) + + LOG.error("Configuration file `config.ini` has been generated. Please fill out all of the necessary information. Refer to the docs for information on what a specific configuration option is.") + exit()
\ No newline at end of file diff --git a/config.ini.example b/config.ini.example new file mode 100644 index 0000000..4563a12 --- /dev/null +++ b/config.ini.example @@ -0,0 +1,21 @@ +[BOT_INFO] +token = +bonus_cooldown = +bot_color = +bug_channel_id = +feedback_channel_id = + +[CRYPTO_COMPARE] +api_key = + +[POSTGRESQL] +username = +password = +host = +port = +database = + +[LAVALINK] +host = +port = +password =
\ No newline at end of file |