diff options
Diffstat (limited to 'code/cogs')
-rw-r--r-- | code/cogs/blackjack.py | 257 | ||||
-rw-r--r-- | code/cogs/coinflip.py | 62 | ||||
-rw-r--r-- | code/cogs/count.py | 40 | ||||
-rw-r--r-- | code/cogs/covid.py | 68 | ||||
-rw-r--r-- | code/cogs/crypto.py | 126 | ||||
-rw-r--r-- | code/cogs/gambling_helpers.py | 139 | ||||
-rw-r--r-- | code/cogs/handlers.py | 124 | ||||
-rw-r--r-- | code/cogs/help.py | 158 | ||||
-rw-r--r-- | code/cogs/info.py | 77 | ||||
-rw-r--r-- | code/cogs/modals.py | 102 | ||||
-rw-r--r-- | code/cogs/moderation.py | 96 | ||||
-rw-r--r-- | code/cogs/music.py | 584 | ||||
-rw-r--r-- | code/cogs/mute.py | 282 | ||||
-rw-r--r-- | code/cogs/owner_info_commands.py | 52 | ||||
-rw-r--r-- | code/cogs/profile.py | 691 | ||||
-rw-r--r-- | code/cogs/slots.py | 81 | ||||
-rw-r--r-- | code/cogs/socketfix.py | 35 | ||||
-rw-r--r-- | code/cogs/tree_sync.py | 33 | ||||
-rw-r--r-- | code/cogs/warnings.py | 121 |
19 files changed, 3128 insertions, 0 deletions
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 |