commit 3c18d552c2c9eb15834b33687915a7b16c98883b Author: Parker Date: Sat Jan 25 22:58:41 2025 -0600 reupload 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 new file mode 100644 index 0000000..6467228 Binary files /dev/null and b/AquaBot.png differ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..31e5a2a --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +

+
+ Aqua Bot Image +
+ Aqua Bot
+

+ +

+ Multipurpse Discord bot made on d.py +

+ +

+ + discord.py + +

+ +# 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. + +
+ +## 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 ` 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 `**\nAllows you to play blackjack with the amount of money bet", inline=False) + embed.add_field(name="**Slots**", value=f"**Usage: `/slots `**\nTake your chances on the slots with a bet of your choice.", inline=False) + embed.add_field(name="**Profile**", value=f"**Usage: `/profile `**\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 `** \nWarn a member for doing something against the rules.", inline=True) + embed.add_field(name="**Delwarn**", value=f"**Usage: `/delwarn `** \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 `** \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