GalaxyCogs/galaxy/galaxy.py
cswimr 99e7d90185
All checks were successful
Actions / Lint Code (Pylint) (push) Successful in 45s
changed all instances of SeaswimmerTheFsh to cswimr
2024-09-16 09:20:13 -04:00

348 lines
24 KiB
Python

import re
from datetime import datetime
from random import randint
import discord
from redbot.core import Config, app_commands, commands
from redbot.core.app_commands import Choice
class Galaxy(commands.Cog):
"""Custom cog intended for use on the Galaxy discord server.
Developed by cswimr."""
def __init__(self, bot):
self.bot = bot
self.config = Config.get_conf(self, identifier=6621962)
self.config.register_guild(
autoreact_target = 0,
autoreact_emoji = '💀'
)
@commands.command()
async def carnagerefund(self, ctx: commands.Context, message_id: str):
"""This command generates a link to refund carnage of killed ships."""
output = f"https://info.galaxy.casa/kills/{message_id}"
await ctx.send(f"Output link: {output}")
@commands.Cog.listener('on_message')
async def gank_won_let_us_flee(self, message: discord.Message):
if message.author.id == 745790085789909033 and message.guild.id == 204965774618656769 and message.channel.id == 753714180900519937:
gank_media_list = [
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174309711577138/gank_won_let_us_flee.gif",
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174310093262951/spin_back_I_dare_you.jpg",
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174310466572328/spin_back_mutt.gif",
"https://cdn.discordapp.com/attachments/1070838419212738621/1155174310806290533/We_dont_run_1s_around_here.gif"
]
if len(message.embeds) == 1 and str(message.embeds[0].color) == '#57f287':
await message.reply(gank_media_list[randint(0,3)])
@commands.Cog.listener('on_message')
async def autoreact(self, message: discord.Message):
if message.guild is not None:
emoji_id = await self.config.guild(message.guild).autoreact_emoji()
if self.check_if_discord_unicode_emoji(emoji_id) is False:
emoji = self.bot.get_emoji(emoji_id)
elif self.check_if_discord_unicode_emoji(emoji_id) is True:
emoji = emoji_id
autoreact_target = await self.config.guild(message.guild).autoreact_target()
if autoreact_target == 0:
return
if not message.author.id == autoreact_target:
return
await message.add_reaction(emoji)
@commands.command(name='autoreact')
@commands.guild_only()
async def autoreact_list(self, ctx: commands.Context):
"""Checks Autoreact's settings."""
emoji_id = await self.config.guild(ctx.guild).autoreact_emoji()
autoreact_target = await self.config.guild(ctx.guild).autoreact_target()
if self.check_if_discord_unicode_emoji(emoji_id) is True:
emoji = emoji_id
embed = discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact is currently set to target <@{autoreact_target}> ({autoreact_target}).\nAutoreact's emoji is currently set to {emoji}.")
else:
emoji = self.bot.get_emoji(emoji_id)
embed = discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact is currently set to target <@{autoreact_target}> ({autoreact_target}).\nAutoreact's emoji is currently set to {emoji} ({await self.config.guild(ctx.guild).autoreact_emoji()}).")
await ctx.send(embed=embed)
autoreact = app_commands.Group(name='autoreact', guild_only=True, description="This group handles the autoreact functionality.")
def check_if_discord_unicode_emoji(self, emoji: str):
emoji_ranges = [
(0x1F600, 0x1F64F), # Emoticons
(0x1F300, 0x1F5FF), # Miscellaneous symbols and pictographs
(0x1F680, 0x1F6FF), # Transport and map symbols
(0x1F700, 0x1F77F), # Alchemical symbols
]
try:
for char in emoji:
code_point = ord(char)
for start, end in emoji_ranges:
if start <= code_point <= end:
return True
except TypeError:
return False
return False
def extract_id(self, input_string):
match = re.search(r'(?<=:)\d+(?=>)', input_string)
if match:
return int(match.group())
return input_string
@autoreact.command(name="emoji")
@app_commands.describe(emoji="Which emoji are you setting Autoreact to use?")
async def autoreact_emoji(self, interaction: discord.Interaction, emoji: str = None):
"""Sets Autoreact's emoji."""
if emoji:
if self.check_if_discord_unicode_emoji(emoji) is True:
await self.config.guild(interaction.guild).autoreactemoji.set(emoji)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact's emoji has been set to {emoji}.")
await interaction.response.send_message(embed=embed)
else:
emoji_id = self.extract_id(input_string=emoji)
for guild in self.bot.guilds:
emoji_to_find = discord.utils.get(guild.emojis, id=emoji_id)
if emoji_to_find:
emoji_obj = emoji_to_find
break
else:
await interaction.response.send_message(content="You're trying to set the autoreact emoji to an emoji I don't have access to!", ephemeral=True)
return
await self.config.guild(interaction.guild).autoreact_emoji.set(emoji_obj.id)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact's emoji has been set to {emoji_obj} ({emoji_obj.id}).")
await interaction.response.send_message(embed=embed)
else:
await self.config.guild(interaction.guild).autoreact_emoji.set('💀')
embed=discord.Embed(color=await self.bot.get_embed_color(None), description="Autoreact's emoji has been set to 💀.")
await interaction.response.send_message(embed=embed)
@autoreact.command(name="set")
@app_commands.describe(member="Who are you targetting?")
async def autoreact_set(self, interaction: discord.Interaction, member: discord.Member):
"""Sets Autoreact's target."""
if member:
await self.config.guild(interaction.guild).autoreact_target.set(member.id)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description=f"Autoreact has been set to automatically react to {member.mention} ({member.id})'s messages.")
await interaction.response.send_message(embed=embed)
else:
await interaction.response.send_message(content="That is not a valid argument!", ephemeral=True)
@autoreact.command(name="reset")
async def autoreact_reset(self, interaction: discord.Interaction):
"""Resets Autoreact's target."""
await self.config.guild(interaction.guild).autoreact_target.set(0)
embed=discord.Embed(color=await self.bot.get_embed_color(None), description="Autoreact's target has been reset.")
await interaction.response.send_message(embed=embed)
@commands.command()
async def unix(self, ctx: commands.Context):
"""Posts the current Unix timestamp."""
timestamp = int(datetime.timestamp(datetime.now()))
embed=discord.Embed(title="Current Time", url="https://www.unixtimestamp.com/", color=await self.bot.get_embed_color(None))
embed.add_field(name="Default", value=f"<t:{timestamp}>\n`<t:{timestamp}>`")
embed.add_field(name="Short Time", value=f"<t:{timestamp}:t>\n`<t:{timestamp}:t>`")
embed.add_field(name="Long Time", value=f"<t:{timestamp}:T>\n`<t:{timestamp}:T>`")
embed.add_field(name="Short Date/Time", value=f"<t:{timestamp}:f>\n`<t:{timestamp}:f>`")
embed.add_field(name="Short Date", value=f"<t:{timestamp}:d>\n`<t:{timestamp}:d>`")
embed.add_field(name="Long Date", value=f"<t:{timestamp}:D>\n`<t:{timestamp}:D>`")
embed.add_field(name="Long Date/Time", value=f"<t:{timestamp}:F>\n`<t:{timestamp}:F>`")
embed.add_field(name="Relative Time", value=f"<t:{timestamp}:R>\n`<t:{timestamp}:R>`")
embed.set_footer(text=f"{timestamp}")
await ctx.send(embed=embed)
await ctx.message.delete()
@commands.group(autohelp=True)
async def insurance(self, ctx: commands.Context):
"""Calculates insurance.
Please only use the value of a ship (from ``/shipinfo``) to calculate insurance and **not** ship cost. Decimals do not work properly with this command, just remove them."""
async def _insurance(self, ship_class: str, value: str):
"""This function does the actual math and configures the embed.
Attributes
-----------
ship_class: Required[:class:`str`]
The class of the ship whose insurance you're checking.
value: Required[:class:`int`]
The value of the ship you're checking. This should be supplied by `/shipinfo`. Not the same as ship cost!"""
cleaned_value = int(''.join(char for char in value if char.isdigit()))
insurance_dict = {
"miner": 0.7,
"freighter": 0.65,
"frigate": 0.6,
"destroyer": 0.55,
"cruiser": 0.5,
"battlecruiser": 0.4,
"battleship": 0.35,
"dreadnought": 0.3,
"carrier": 0.3,
"super_capital": 0.25
}
try:
insurance_dict[f'{ship_class}']
except KeyError as error:
raise ValueError("Received value is not a valid ship class!") from error
if ship_class == "super_capital":
humanized_class = ship_class.replace("_", " ").title()
else:
humanized_class = ship_class.capitalize()
insurance_amount = (f"{round(cleaned_value * insurance_dict[f'{ship_class}']):,}")
value_output = (f'{cleaned_value:,}')
embed = discord.Embed(title="Insurance Payout", color=await self.bot.get_embed_color(None))
embed.add_field(name="Ship Class", value=f"{humanized_class}", inline=False)
embed.add_field(name="Ship Value", value=f"{value_output}", inline=False)
embed.add_field(name="Insurance Amount", value=f"{insurance_amount}", inline=False)
return embed
@insurance.command()
async def miner(self, ctx: commands.Context, value):
"""Calculates insurance for miners. (70%)"""
await ctx.send(embed=await self._insurance('miner', value))
@insurance.command()
async def freighter(self, ctx: commands.Context, value):
"""Calculates insurance for freighters. (65%)"""
await ctx.send(embed=await self._insurance('freighter', value))
@insurance.command()
async def frigate(self, ctx: commands.Context, value):
"""Calculates insurance for frigates. (60%)"""
await ctx.send(embed=await self._insurance('frigate', value))
@insurance.command()
async def destroyer(self, ctx: commands.Context, value):
"""Calculates insurance for destroyers. (55%)"""
await ctx.send(embed=await self._insurance('destroyer', value))
@insurance.command()
async def cruiser(self, ctx: commands.Context, value):
"""Calculates insurance for cruisers. (50%)"""
await ctx.send(embed=await self._insurance('cruiser', value))
@insurance.command()
async def battlecruiser(self, ctx: commands.Context, value):
"""Calculates insurance for battlecruisers. (40%)"""
await ctx.send(embed=await self._insurance('battlecruiser', value))
@insurance.command()
async def battleship(self, ctx: commands.Context, value):
"""Calculates insurance for battleships. (35%)"""
await ctx.send(embed=await self._insurance('battleship', value))
@insurance.command()
async def dreadnought(self, ctx: commands.Context, value):
"""Calculates insurance for dreadnoughts. (30%)"""
await ctx.send(embed=await self._insurance('dreadnought', value))
@insurance.command()
async def carrier(self, ctx: commands.Context, value):
"""Calculates insurance for carriers. (30%)"""
await ctx.send(embed=await self._insurance('carrier', value))
@insurance.command()
async def supercapital(self, ctx: commands.Context, value):
"""Calculates insurance for super capitals. (25%)"""
await ctx.send(embed=await self._insurance('super_capital', value))
@commands.command(aliases=["wh"])
async def warehouse(self, ctx: commands.Context, lvlfrom: int = 1, lvlto: int = 38):
"""Calculates the total cost to upgrade your warehouse from a level to a level."""
warehouse_levels = {1:0, 2:1000,3:2500,4:4500,5:7500,6:12500,7:20000,8:31500,9:46500,10:65500,11:87500,12:113500,13:143500,14:178500,15:218500,16:263500,17:313500,18:373500,19:443500,20:523500,21:613500,22:713500,23:823500,24:943500,25:1073500,26:1223500,27:1398500,28:1598500,29:1823500,30:2073500,31:2353500, 32:2663500, 33:3003500, 34:3373500, 35:3773500, 36:4193500, 37:4644500, 38:5093500}
total_from = (f'{warehouse_levels[lvlfrom]:,}')
total_to = (f'{warehouse_levels[lvlto]:,}')
output = warehouse_levels[lvlto] - warehouse_levels[lvlfrom]
total = (f'{output:,}')
embed = discord.Embed(title="Warehouse Cost", color=await self.bot.get_embed_color(None))
embed.add_field(name="From:", value=f"Warehouse Level: {lvlfrom}\nTotal Cost: {total_from} Credits", inline=False)
embed.add_field(name="To:", value=f"Warehouse Level: {lvlto}\nTotal Cost: {total_to} Credits", inline=False)
embed.add_field(name="Output:", value=f"{total} Credits", inline=False)
if lvlfrom == lvlto:
await ctx.send(contents="``lvlfrom`` cannot be the same as ``lvlto``.")
elif lvlfrom > lvlto:
await ctx.send(contents="``lvlfrom`` cannot be a higher value than ``to``.")
elif lvlfrom < 1 or lvlfrom > 37:
await ctx.send(contents="``lvlfrom`` must be higher than 0 and lower than 38")
elif lvlto < 1 or lvlto > 38:
await ctx.send(contents="``lvlto`` must be higher than 1 and lower than 39")
else:
await ctx.send(embed=embed)
await ctx.message.delete()
@app_commands.command()
@app_commands.describe(answer='Which answer are you trying to post?')
@app_commands.choices(answer=[
Choice(name='Important Links', value='links'),
Choice(name='DPS Calculations', value='dps'),
Choice(name='Reward Roles', value='reward_roles'),
Choice(name='NPC Intervals', value='npc_intervals'),
Choice(name='Linked Role', value='linked_role'),
Choice(name='RoPro', value='ropro')
])
async def faq(self, interaction: discord.Interaction, answer: Choice[str], member: discord.Member = None):
"""Posts answers to frequently asked questions."""
embed = None
embed_secondary = None
if answer.value == 'dps':
embed = discord.Embed(title="DPS Calculations", color=await self.bot.get_embed_color(None), description="The ``/info`` command (and by extention ``/shipinfo`` from Odin) misreports DPS, due to it calculating DPS disregarding the turret's type (kinetic, laser), causing it to assume the target ship is both hulled and has shield simultaneously. It also ignores turret overrides, custom reloads, and custom damage values. If you'd like to check ship stats accurately, you can either use the ``/ship`` command in this channel or you can use the [Galaxy Info Website](https://info.galaxy.casa/ships). Alternatively, to check turret stats, you can use the [Galaxy Info Turrets Page](https://info.galaxy.casa/turrets).")
elif answer.value == 'links':
embed = discord.Embed(title="Important Links", color=await self.bot.get_embed_color(None))
embed.add_field(name="Galaxy", value="[Galaxy Discord](https://discord.com/invite/robloxgalaxy)\n[Galaxy Support](https://discord.com/invite/ShWshkhYhZ)")
embed.add_field(name="Galaxypedia", value="[Galaxypedia Website](https://robloxgalaxy.wiki/wiki/Main_Page)\n[Galaxypedia Discord](https://discord.robloxgalaxy.wiki/)")
elif answer.value == 'npc_intervals':
embed = discord.Embed(title="NPC Spawn Intervals", color=await self.bot.get_embed_color(None), description="*Disclaimer: Spawn times may be different if EventID is active!*")
embed.add_field(name="Every 6.7 Minutes", value="[Dragoon](https://robloxgalaxy.wiki/wiki/Dragoon) *(80% Chance)*")
embed.add_field(name="Every 8.3 Minutes", value="[Swarmer](https://robloxgalaxy.wiki/wiki/Swarmer) *(33% Chance)*")
embed.add_field(name="Every 10 Minutes", value="[Jormungand](https://robloxgalaxy.wiki/wiki/Jormungand) *(75% Chance)*")
embed.add_field(name="Every 12.5 Minutes", value="[Bruiser](https://robloxgalaxy.wiki/wiki/Bruiser) *(50% Chance)*")
embed.add_field(name="Every 16.7 Minutes", value="[Outrider](https://robloxgalaxy.wiki/wiki/Outrider) *(50% Chance)*")
embed.add_field(name="Every 28.3 Minutes", value="[Punisher](https://robloxgalaxy.wiki/wiki/Punisher)")
embed.add_field(name="Every 60 Minutes", value="[X-0](https://robloxgalaxy.wiki/wiki/X-0) *(45% Chance)*\n[Decimator](https://robloxgalaxy.wiki/wiki/Decimator)")
embed.add_field(name="Every 70 Minutes", value="[Galleon](https://robloxgalaxy.wiki/wiki/Galleon)")
embed.add_field(name="Every 120 Minutes", value="[Kodiak](https://robloxgalaxy.wiki/wiki/Kodiak)")
elif answer.value == 'linked_role':
embed = discord.Embed(title="Desktop / Web", color=await self.bot.get_embed_color(None), description="**Step 1:** Open the Server Dropdown menu in the top-left by clicking on the server's name.\n\n**Step 2:** Click the \"*Linked Roles*\" button.\n\n**Step 3:** Click on \"*Linked*.\"\n\n**Step 4:** Click \"*Finish*.\" You're done!\n*Note: You should already be Verified on Bloxlink. If you are not, go to the verification channel to verify.*")
embed.set_thumbnail(url="https://cdn.discordapp.com/attachments/1070838419212738621/1079927564421836930/image.png")
embed_secondary = discord.Embed(title="Mobile", color=await self.bot.get_embed_color(None), description="**Step 1:** Open the Server menu on the top of the channel list by tapping the server's name.\n\n**Step 2:** Scroll down and tap the \"*Linked Roles*\" button.\n\n**Step 3:** Tap on \"*Linked*.\"\n\n**Step 4:** Tap \"*Finish*.\" You're done!\n*Note: You should already be Verified on Bloxlink. If you are not, go to the verification channel to verify.*")
embed_secondary.set_thumbnail(url="https://cdn.discordapp.com/attachments/1047347377348030494/1079930169562771576/Screenshot_20230227_195338_Discord.jpg")
elif answer.value == 'reward_roles':
embed = discord.Embed(title="Reward Roles", color=await self.bot.get_embed_color(None))
embed.add_field(name="Picture Perms", value="Level 6")
embed.add_field(name="Suggestions", value="Level 8")
embed.add_field(name="DJ", value="Level 10")
embed.add_field(name="Reaction Perms", value="Level 20")
embed.add_field(name="External Emoji Perms", value="Level 30")
embed.set_footer(text="Use `-profile` to get your current level.")
elif answer.value == 'ropro':
embed = discord.Embed(title="RoPro", url="https://ropro.io", color=await self.bot.get_embed_color(None), description="""[RoPro](https://ropro.io) is a browser extension that tracks ROBLOX playtime, enhances your profile, and provides other useful utilities. **Please keep in mind that RoPro only tracks playtime from AFTER you install the extension.**""")
content = member.mention if member else None
await interaction.response.send_message(content="> The rigid requirement for bots to compulsorily respond to interactions in Discord, such as slash commands or application commands, is an irksome limitation that curtails the flexibility and natural flow of interactions. This forced response paradigm undermines the very essence of automation and intelligent design that bots were intended to offer. There are instances where silence or lack of response is not only acceptable but also desired, aligning with the nuanced dynamics of human communication. Discord's insistence on a response, even when it serves no purpose, imposes unnecessary complexity and verbosity, creating an environment where superfluous replies dilute the efficiency and elegance of bot-driven interactions. This constraint highlights the importance of granting bot developers the autonomy to determine the most suitable course of action based on context, contributing to a more seamless and user-centric experience within the Discord ecosystem.\n - ChatGPT", ephemeral=True)
response: discord.InteractionMessage = await interaction.original_response()
await response.delete()
if embed_secondary:
await interaction.channel.send(content=content, embeds=[embed, embed_secondary])
else:
await interaction.channel.send(content=content, embed=embed)
# @faq.command(name="polaris_switch")
# @checks.admin()
# async def faq_polaris_switch(self, ctx, member: discord.Member = None):
# """Posts an embed on the switch to the Polaris bot."""
# embed=discord.Embed(title="Polaris FAQ", color=await self.bot.get_embed_color(None), description="As you probably know, we've decided to switch to the Polaris bot for leveling/xp, as opposed to Tatsu.\nThere are many reasons for this, which will be explained below.")
# embed.add_field(name="Problems with Tatsu", value="1: Tatsu does not provide nearly as much configuration potential as Polaris does. An example of this is Polaris' customizable Level Curve.\n\n2: Tatsu does not have channel/role modifiers.\n\n3: Tatsu does not have actual levels, instead it has unconfigurable \"Global XP\", which gives \"Global Levels\". You cannot do anything with Global XP aside from blacklisting channels where people can gain it, like a bot-commands channel or something like that.\n\n4: Tatsu's leaderboard sucks, and only shows the top 10 on the web version.\n\n5: Tatsu has no XP management commands.\n\n6: Tatsu has TONS of bloat/useless commands, making the bot harder to configure.", inline=False)
# embed.add_field(name="Polaris' Features", value="1: Polaris allows you to customize the level curve of your server, and provides presets to make the transition easier.\n\n2: Polaris has XP management commands.\n\n3: Polaris has way more configuration in terms of Reward Roles.\n\n4: Polaris allows you to customize the level-up message shown whenever people achieve the next level.\n\n5: Polaris has both role and channel modifiers.\n\n6: Polaris' leaderboard is excellent, showing the top 1,000 ranked users on the same webpage, and allowing you to see your own stats, progress towards your next reward role, and all 350 levels and your progress towards them.\n\n7: Polaris is **just** a leveling bot. You don't have to deal with any of the bloat of multi-purpose bots like Tatsu or MEE6, you only get what you actually need.", inline=False)
# embed.add_field(name="Conclusion",value="With all of that said, you're probably wondering why we're putting so much effort into transferring peoples' data to the new bot.\n\nWell, Tatsu has been going since 2020, and I don't particularly favor the idea of clearing everyone's XP, especially when people have built up reward roles from Tatsu already, like Picture Perms, Suggestions access, and DJ.\n\nWith all this in mind, I hope this isn't too much of an inconvenience for you all, as I tried to make the process as seamless as possible without having to update all 10,000 people in the server.", inline=False)
# if member:
# await ctx.channel.send(embed=embed, content=member.mention)
# else:
# await ctx.channel.send(embed=embed)
# await ctx.message.delete()
@warehouse.error
@unix.error
async def faq_handler(self, error):
"""Error Handler for Galaxy."""
if isinstance(error, discord.NotFound):
return