Do a black pass and add black to contributing guidelines

FORK MAINTAINERS: I'm so, so sorry, but this was planned since forever.
If you need help integrating this, feel free to contact me.
This commit is contained in:
Ave Ozkal 2020-04-21 01:05:32 +03:00
parent 38d8a4ce5f
commit fd845ecb40
No known key found for this signature in database
GPG key ID: 398DD7BD03276F6D
27 changed files with 1521 additions and 1203 deletions

View file

@ -31,6 +31,14 @@ If you're moving from Kurisu/Robocop, and want to preserve your data, you'll wan
--- ---
## Contributing
Contributions are welcome. If you're unsure if your PR would be merged or not, either open an issue, ask on ReSwitched off-topic pinging ave or DM ave.
You're expected to use [black](https://github.com/psf/black) for code formatting before sending a PR. Simply install it with pip (`pip3 install black`), and run it with `black .`.
---
## Credits ## Credits
Robocop-NG was initially developed by @aveao and @tumGER. It is currently maintained by @aveao. Similarly, the official robocop-ng on reswitched discord guild is hosted by @aveao too. Robocop-NG was initially developed by @aveao and @tumGER. It is currently maintained by @aveao. Similarly, the official robocop-ng on reswitched discord guild is hosted by @aveao too.

View file

@ -9,7 +9,7 @@ import config
import discord import discord
from discord.ext import commands from discord.ext import commands
script_name = os.path.basename(__file__).split('.')[0] script_name = os.path.basename(__file__).split(".")[0]
log_file_name = f"{script_name}.log" log_file_name = f"{script_name}.log"
@ -17,15 +17,17 @@ log_file_name = f"{script_name}.log"
max_file_size = 1000 * 1000 * 8 max_file_size = 1000 * 1000 * 8
backup_count = 3 backup_count = 3
file_handler = logging.handlers.RotatingFileHandler( file_handler = logging.handlers.RotatingFileHandler(
filename=log_file_name, maxBytes=max_file_size, backupCount=backup_count) filename=log_file_name, maxBytes=max_file_size, backupCount=backup_count
)
stdout_handler = logging.StreamHandler(sys.stdout) stdout_handler = logging.StreamHandler(sys.stdout)
log_format = logging.Formatter( log_format = logging.Formatter(
'[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s') "[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
)
file_handler.setFormatter(log_format) file_handler.setFormatter(log_format)
stdout_handler.setFormatter(log_format) stdout_handler.setFormatter(log_format)
log = logging.getLogger('discord') log = logging.getLogger("discord")
log.setLevel(logging.INFO) log.setLevel(logging.INFO)
log.addHandler(file_handler) log.addHandler(file_handler)
log.addHandler(stdout_handler) log.addHandler(stdout_handler)
@ -37,13 +39,14 @@ def get_prefix(bot, message):
return commands.when_mentioned_or(*prefixes)(bot, message) return commands.when_mentioned_or(*prefixes)(bot, message)
wanted_jsons = ["data/restrictions.json", wanted_jsons = [
"data/robocronptab.json", "data/restrictions.json",
"data/userlog.json", "data/robocronptab.json",
"data/invites.json"] "data/userlog.json",
"data/invites.json",
]
bot = commands.Bot(command_prefix=get_prefix, bot = commands.Bot(command_prefix=get_prefix, description=config.bot_description)
description=config.bot_description)
bot.help_command = commands.DefaultHelpCommand(dm_help=True) bot.help_command = commands.DefaultHelpCommand(dm_help=True)
bot.log = log bot.log = log
@ -51,12 +54,12 @@ bot.config = config
bot.script_name = script_name bot.script_name = script_name
bot.wanted_jsons = wanted_jsons bot.wanted_jsons = wanted_jsons
if __name__ == '__main__': if __name__ == "__main__":
for cog in config.initial_cogs: for cog in config.initial_cogs:
try: try:
bot.load_extension(cog) bot.load_extension(cog)
except: except:
log.error(f'Failed to load cog {cog}.') log.error(f"Failed to load cog {cog}.")
log.error(traceback.print_exc()) log.error(traceback.print_exc())
@ -67,31 +70,37 @@ async def on_ready():
bot.app_info = await bot.application_info() bot.app_info = await bot.application_info()
bot.botlog_channel = bot.get_channel(config.botlog_channel) bot.botlog_channel = bot.get_channel(config.botlog_channel)
log.info(f'\nLogged in as: {bot.user.name} - ' log.info(
f'{bot.user.id}\ndpy version: {discord.__version__}\n') f"\nLogged in as: {bot.user.name} - "
f"{bot.user.id}\ndpy version: {discord.__version__}\n"
)
game_name = f"{config.prefixes[0]}help" game_name = f"{config.prefixes[0]}help"
# Send "Robocop has started! x has y members!" # Send "Robocop has started! x has y members!"
guild = bot.botlog_channel.guild guild = bot.botlog_channel.guild
msg = f"{bot.user.name} has started! "\ msg = (
f"{guild.name} has {guild.member_count} members!" f"{bot.user.name} has started! "
f"{guild.name} has {guild.member_count} members!"
)
data_files = [discord.File(fpath) for fpath in wanted_jsons] data_files = [discord.File(fpath) for fpath in wanted_jsons]
await bot.botlog_channel.send(msg, files=data_files) await bot.botlog_channel.send(msg, files=data_files)
activity = discord.Activity(name=game_name, activity = discord.Activity(name=game_name, type=discord.ActivityType.listening)
type=discord.ActivityType.listening)
await bot.change_presence(activity=activity) await bot.change_presence(activity=activity)
@bot.event @bot.event
async def on_command(ctx): async def on_command(ctx):
log_text = f"{ctx.message.author} ({ctx.message.author.id}): "\ log_text = (
f"\"{ctx.message.content}\" " f"{ctx.message.author} ({ctx.message.author.id}): " f'"{ctx.message.content}" '
)
if ctx.guild: # was too long for tertiary if if ctx.guild: # was too long for tertiary if
log_text += f"on \"{ctx.channel.name}\" ({ctx.channel.id}) "\ log_text += (
f"at \"{ctx.guild.name}\" ({ctx.guild.id})" f'on "{ctx.channel.name}" ({ctx.channel.id}) '
f'at "{ctx.guild.name}" ({ctx.guild.id})'
)
else: else:
log_text += f"on DMs ({ctx.channel.id})" log_text += f"on DMs ({ctx.channel.id})"
log.info(log_text) log.info(log_text)
@ -106,9 +115,11 @@ async def on_error(event_method, *args, **kwargs):
async def on_command_error(ctx, error): async def on_command_error(ctx, error):
error_text = str(error) error_text = str(error)
err_msg = f"Error with \"{ctx.message.content}\" from "\ err_msg = (
f"\"{ctx.message.author} ({ctx.message.author.id}) "\ f'Error with "{ctx.message.content}" from '
f"of type {type(error)}: {error_text}" f'"{ctx.message.author} ({ctx.message.author.id}) '
f"of type {type(error)}: {error_text}"
)
log.error(err_msg) log.error(err_msg)
@ -119,45 +130,60 @@ async def on_command_error(ctx, error):
if isinstance(error, commands.NoPrivateMessage): if isinstance(error, commands.NoPrivateMessage):
return await ctx.send("This command doesn't work on DMs.") return await ctx.send("This command doesn't work on DMs.")
elif isinstance(error, commands.MissingPermissions): elif isinstance(error, commands.MissingPermissions):
roles_needed = '\n- '.join(error.missing_perms) roles_needed = "\n- ".join(error.missing_perms)
return await ctx.send(f"{ctx.author.mention}: You don't have the right" return await ctx.send(
" permissions to run this command. You need: " f"{ctx.author.mention}: You don't have the right"
f"```- {roles_needed}```") " permissions to run this command. You need: "
f"```- {roles_needed}```"
)
elif isinstance(error, commands.BotMissingPermissions): elif isinstance(error, commands.BotMissingPermissions):
roles_needed = '\n-'.join(error.missing_perms) roles_needed = "\n-".join(error.missing_perms)
return await ctx.send(f"{ctx.author.mention}: Bot doesn't have " return await ctx.send(
"the right permissions to run this command. " f"{ctx.author.mention}: Bot doesn't have "
"Please add the following roles: " "the right permissions to run this command. "
f"```- {roles_needed}```") "Please add the following roles: "
f"```- {roles_needed}```"
)
elif isinstance(error, commands.CommandOnCooldown): elif isinstance(error, commands.CommandOnCooldown):
return await ctx.send(f"{ctx.author.mention}: You're being " return await ctx.send(
"ratelimited. Try in " f"{ctx.author.mention}: You're being "
f"{error.retry_after:.1f} seconds.") "ratelimited. Try in "
f"{error.retry_after:.1f} seconds."
)
elif isinstance(error, commands.CheckFailure): elif isinstance(error, commands.CheckFailure):
return await ctx.send(f"{ctx.author.mention}: Check failed. " return await ctx.send(
"You might not have the right permissions " f"{ctx.author.mention}: Check failed. "
"to run this command, or you may not be able " "You might not have the right permissions "
"to run this command in the current channel.") "to run this command, or you may not be able "
elif isinstance(error, commands.CommandInvokeError) and\ "to run this command in the current channel."
("Cannot send messages to this user" in error_text): )
return await ctx.send(f"{ctx.author.mention}: I can't DM you.\n" elif isinstance(error, commands.CommandInvokeError) and (
"You might have me blocked or have DMs " "Cannot send messages to this user" in error_text
f"blocked globally or for {ctx.guild.name}.\n" ):
"Please resolve that, then " return await ctx.send(
"run the command again.") f"{ctx.author.mention}: I can't DM you.\n"
"You might have me blocked or have DMs "
f"blocked globally or for {ctx.guild.name}.\n"
"Please resolve that, then "
"run the command again."
)
elif isinstance(error, commands.CommandNotFound): elif isinstance(error, commands.CommandNotFound):
# Nothing to do when command is not found. # Nothing to do when command is not found.
return return
help_text = f"Usage of this command is: ```{ctx.prefix}{ctx.command.name} "\ help_text = (
f"{ctx.command.signature}```\nPlease see `{ctx.prefix}help "\ f"Usage of this command is: ```{ctx.prefix}{ctx.command.name} "
f"{ctx.command.name}` for more info about this command." f"{ctx.command.signature}```\nPlease see `{ctx.prefix}help "
f"{ctx.command.name}` for more info about this command."
)
if isinstance(error, commands.BadArgument): if isinstance(error, commands.BadArgument):
return await ctx.send(f"{ctx.author.mention}: You gave incorrect " return await ctx.send(
f"arguments. {help_text}") f"{ctx.author.mention}: You gave incorrect " f"arguments. {help_text}"
)
elif isinstance(error, commands.MissingRequiredArgument): elif isinstance(error, commands.MissingRequiredArgument):
return await ctx.send(f"{ctx.author.mention}: You gave incomplete " return await ctx.send(
f"arguments. {help_text}") f"{ctx.author.mention}: You gave incomplete " f"arguments. {help_text}"
)
@bot.event @bot.event
@ -171,13 +197,15 @@ async def on_message(message):
# Ignore messages in newcomers channel, unless it's potentially # Ignore messages in newcomers channel, unless it's potentially
# an allowed command # an allowed command
welcome_allowed = ["reset", "kick", "ban", "warn"] welcome_allowed = ["reset", "kick", "ban", "warn"]
if message.channel.id == config.welcome_channel and\ if message.channel.id == config.welcome_channel and not any(
not any(cmd in message.content for cmd in welcome_allowed): cmd in message.content for cmd in welcome_allowed
):
return return
ctx = await bot.get_context(message) ctx = await bot.get_context(message)
await bot.invoke(ctx) await bot.invoke(ctx)
if not os.path.exists("data"): if not os.path.exists("data"):
os.makedirs("data") os.makedirs("data")

View file

@ -16,7 +16,7 @@ class Admin(Cog):
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_bot_manager) @commands.check(check_if_bot_manager)
@commands.command(name='exit', aliases=["quit", "bye"]) @commands.command(name="exit", aliases=["quit", "bye"])
async def _exit(self, ctx): async def _exit(self, ctx):
"""Shuts down the bot, bot manager only.""" """Shuts down the bot, bot manager only."""
await ctx.send(":wave: Goodbye!") await ctx.send(":wave: Goodbye!")
@ -27,8 +27,10 @@ class Admin(Cog):
@commands.command() @commands.command()
async def fetchlog(self, ctx): async def fetchlog(self, ctx):
"""Returns log""" """Returns log"""
await ctx.send("Here's the current log file:", await ctx.send(
file=discord.File(f"{self.bot.script_name}.log")) "Here's the current log file:",
file=discord.File(f"{self.bot.script_name}.log"),
)
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_bot_manager) @commands.check(check_if_bot_manager)
@ -40,32 +42,29 @@ class Admin(Cog):
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_bot_manager) @commands.check(check_if_bot_manager)
@commands.command(name='eval') @commands.command(name="eval")
async def _eval(self, ctx, *, code: str): async def _eval(self, ctx, *, code: str):
"""Evaluates some code, bot manager only.""" """Evaluates some code, bot manager only."""
try: try:
code = code.strip('` ') code = code.strip("` ")
env = { env = {
'bot': self.bot, "bot": self.bot,
'ctx': ctx, "ctx": ctx,
'message': ctx.message, "message": ctx.message,
'server': ctx.guild, "server": ctx.guild,
'guild': ctx.guild, "guild": ctx.guild,
'channel': ctx.message.channel, "channel": ctx.message.channel,
'author': ctx.message.author, "author": ctx.message.author,
# modules # modules
'discord': discord, "discord": discord,
'commands': commands, "commands": commands,
# utilities # utilities
'_get': discord.utils.get, "_get": discord.utils.get,
'_find': discord.utils.find, "_find": discord.utils.find,
# last result # last result
'_': self.last_eval_result, "_": self.last_eval_result,
'_p': self.previous_eval_code, "_p": self.previous_eval_code,
} }
env.update(globals()) env.update(globals())
@ -79,16 +78,15 @@ class Admin(Cog):
self.previous_eval_code = code self.previous_eval_code = code
sliced_message = await self.bot.slice_message(repr(result), sliced_message = await self.bot.slice_message(
prefix="```", repr(result), prefix="```", suffix="```"
suffix="```") )
for msg in sliced_message: for msg in sliced_message:
await ctx.send(msg) await ctx.send(msg)
except: except:
sliced_message = \ sliced_message = await self.bot.slice_message(
await self.bot.slice_message(traceback.format_exc(), traceback.format_exc(), prefix="```", suffix="```"
prefix="```", )
suffix="```")
for msg in sliced_message: for msg in sliced_message:
await ctx.send(msg) await ctx.send(msg)
@ -102,22 +100,25 @@ class Admin(Cog):
@commands.command() @commands.command()
async def pull(self, ctx, auto=False): async def pull(self, ctx, auto=False):
"""Does a git pull, bot manager only.""" """Does a git pull, bot manager only."""
tmp = await ctx.send('Pulling...') tmp = await ctx.send("Pulling...")
git_output = await self.bot.async_call_shell("git pull") git_output = await self.bot.async_call_shell("git pull")
await tmp.edit(content=f"Pull complete. Output: ```{git_output}```") await tmp.edit(content=f"Pull complete. Output: ```{git_output}```")
if auto: if auto:
cogs_to_reload = re.findall(r'cogs/([a-z_]*).py[ ]*\|', git_output) cogs_to_reload = re.findall(r"cogs/([a-z_]*).py[ ]*\|", git_output)
for cog in cogs_to_reload: for cog in cogs_to_reload:
try: try:
self.bot.unload_extension("cogs." + cog) self.bot.unload_extension("cogs." + cog)
self.bot.load_extension("cogs." + cog) self.bot.load_extension("cogs." + cog)
self.bot.log.info(f'Reloaded ext {cog}') self.bot.log.info(f"Reloaded ext {cog}")
await ctx.send(f':white_check_mark: `{cog}` ' await ctx.send(
'successfully reloaded.') f":white_check_mark: `{cog}` " "successfully reloaded."
)
await self.cog_load_actions(cog) await self.cog_load_actions(cog)
except: except:
await ctx.send(f':x: Cog reloading failed, traceback: ' await ctx.send(
f'```\n{traceback.format_exc()}\n```') f":x: Cog reloading failed, traceback: "
f"```\n{traceback.format_exc()}\n```"
)
return return
@commands.guild_only() @commands.guild_only()
@ -129,11 +130,13 @@ class Admin(Cog):
self.bot.load_extension("cogs." + ext) self.bot.load_extension("cogs." + ext)
await self.cog_load_actions(ext) await self.cog_load_actions(ext)
except: except:
await ctx.send(f':x: Cog loading failed, traceback: ' await ctx.send(
f'```\n{traceback.format_exc()}\n```') f":x: Cog loading failed, traceback: "
f"```\n{traceback.format_exc()}\n```"
)
return return
self.bot.log.info(f'Loaded ext {ext}') self.bot.log.info(f"Loaded ext {ext}")
await ctx.send(f':white_check_mark: `{ext}` successfully loaded.') await ctx.send(f":white_check_mark: `{ext}` successfully loaded.")
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_bot_manager) @commands.check(check_if_bot_manager)
@ -141,8 +144,8 @@ class Admin(Cog):
async def unload(self, ctx, ext: str): async def unload(self, ctx, ext: str):
"""Unloads a cog, bot manager only.""" """Unloads a cog, bot manager only."""
self.bot.unload_extension("cogs." + ext) self.bot.unload_extension("cogs." + ext)
self.bot.log.info(f'Unloaded ext {ext}') self.bot.log.info(f"Unloaded ext {ext}")
await ctx.send(f':white_check_mark: `{ext}` successfully unloaded.') await ctx.send(f":white_check_mark: `{ext}` successfully unloaded.")
@commands.check(check_if_bot_manager) @commands.check(check_if_bot_manager)
@commands.command() @commands.command()
@ -158,11 +161,13 @@ class Admin(Cog):
self.bot.load_extension("cogs." + ext) self.bot.load_extension("cogs." + ext)
await self.cog_load_actions(ext) await self.cog_load_actions(ext)
except: except:
await ctx.send(f':x: Cog reloading failed, traceback: ' await ctx.send(
f'```\n{traceback.format_exc()}\n```') f":x: Cog reloading failed, traceback: "
f"```\n{traceback.format_exc()}\n```"
)
return return
self.bot.log.info(f'Reloaded ext {ext}') self.bot.log.info(f"Reloaded ext {ext}")
await ctx.send(f':white_check_mark: `{ext}` successfully reloaded.') await ctx.send(f":white_check_mark: `{ext}` successfully reloaded.")
def setup(bot): def setup(bot):

View file

@ -32,50 +32,51 @@ class Basic(Cog):
async def communitycount(self, ctx): async def communitycount(self, ctx):
"""Prints the community member count of the server.""" """Prints the community member count of the server."""
community = ctx.guild.get_role(config.named_roles["community"]) community = ctx.guild.get_role(config.named_roles["community"])
await ctx.send(f"{ctx.guild.name} has " await ctx.send(
f"{len(community.members)} community members!") f"{ctx.guild.name} has " f"{len(community.members)} community members!"
)
@commands.guild_only() @commands.guild_only()
@commands.command() @commands.command()
async def hackercount(self, ctx): async def hackercount(self, ctx):
"""Prints the hacker member count of the server.""" """Prints the hacker member count of the server."""
h4x0r = ctx.guild.get_role(config.named_roles["hacker"]) h4x0r = ctx.guild.get_role(config.named_roles["hacker"])
await ctx.send(f"{ctx.guild.name} has " await ctx.send(
f"{len(h4x0r.members)} people with hacker role!") f"{ctx.guild.name} has " f"{len(h4x0r.members)} people with hacker role!"
)
@commands.guild_only() @commands.guild_only()
@commands.command() @commands.command()
async def membercount(self, ctx): async def membercount(self, ctx):
"""Prints the member count of the server.""" """Prints the member count of the server."""
await ctx.send(f"{ctx.guild.name} has " await ctx.send(f"{ctx.guild.name} has " f"{ctx.guild.member_count} members!")
f"{ctx.guild.member_count} members!")
@commands.command(aliases=["robocopng", "robocop-ng"]) @commands.command(aliases=["robocopng", "robocop-ng"])
async def robocop(self, ctx): async def robocop(self, ctx):
"""Shows a quick embed with bot info.""" """Shows a quick embed with bot info."""
embed = discord.Embed(title="Robocop-NG", embed = discord.Embed(
url=config.source_url, title="Robocop-NG", url=config.source_url, description=config.embed_desc
description=config.embed_desc) )
embed.set_thumbnail(url=self.bot.user.avatar_url) embed.set_thumbnail(url=self.bot.user.avatar_url)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.command(aliases=['p']) @commands.command(aliases=["p"])
async def ping(self, ctx): async def ping(self, ctx):
"""Shows ping values to discord. """Shows ping values to discord.
RTT = Round-trip time, time taken to send a message to discord RTT = Round-trip time, time taken to send a message to discord
GW = Gateway Ping""" GW = Gateway Ping"""
before = time.monotonic() before = time.monotonic()
tmp = await ctx.send('Calculating ping...') tmp = await ctx.send("Calculating ping...")
after = time.monotonic() after = time.monotonic()
rtt_ms = (after - before) * 1000 rtt_ms = (after - before) * 1000
gw_ms = self.bot.latency * 1000 gw_ms = self.bot.latency * 1000
message_text = f":ping_pong:\n"\ message_text = (
f"rtt: `{rtt_ms:.1f}ms`\n"\ f":ping_pong:\n" f"rtt: `{rtt_ms:.1f}ms`\n" f"gw: `{gw_ms:.1f}ms`"
f"gw: `{gw_ms:.1f}ms`" )
self.bot.log.info(message_text) self.bot.log.info(message_text)
await tmp.edit(content=message_text) await tmp.edit(content=message_text)

View file

@ -31,9 +31,14 @@ class Common(Cog):
res_timestamp = math.floor(time.mktime(time_struct)) res_timestamp = math.floor(time.mktime(time_struct))
return res_timestamp return res_timestamp
def get_relative_timestamp(self, time_from=None, time_to=None, def get_relative_timestamp(
humanized=False, include_from=False, self,
include_to=False): time_from=None,
time_to=None,
humanized=False,
include_from=False,
include_to=False,
):
# Setting default value to utcnow() makes it show time from cog load # Setting default value to utcnow() makes it show time from cog load
# which is not what we want # which is not what we want
if not time_from: if not time_from:
@ -43,17 +48,19 @@ class Common(Cog):
if humanized: if humanized:
humanized_string = humanize.naturaltime(time_from - time_to) humanized_string = humanize.naturaltime(time_from - time_to)
if include_from and include_to: if include_from and include_to:
str_with_from_and_to = f"{humanized_string} "\ str_with_from_and_to = (
f"({str(time_from).split('.')[0]} "\ f"{humanized_string} "
f"- {str(time_to).split('.')[0]})" f"({str(time_from).split('.')[0]} "
f"- {str(time_to).split('.')[0]})"
)
return str_with_from_and_to return str_with_from_and_to
elif include_from: elif include_from:
str_with_from = f"{humanized_string} "\ str_with_from = (
f"({str(time_from).split('.')[0]})" f"{humanized_string} " f"({str(time_from).split('.')[0]})"
)
return str_with_from return str_with_from
elif include_to: elif include_to:
str_with_to = f"{humanized_string} "\ str_with_to = f"{humanized_string} " f"({str(time_to).split('.')[0]})"
f"({str(time_to).split('.')[0]})"
return str_with_to return str_with_to
return humanized_string return humanized_string
else: else:
@ -61,8 +68,7 @@ class Common(Cog):
epoch_from = (time_from - epoch).total_seconds() epoch_from = (time_from - epoch).total_seconds()
epoch_to = (time_to - epoch).total_seconds() epoch_to = (time_to - epoch).total_seconds()
second_diff = epoch_to - epoch_from second_diff = epoch_to - epoch_from
result_string = str(datetime.timedelta( result_string = str(datetime.timedelta(seconds=second_diff)).split(".")[0]
seconds=second_diff)).split('.')[0]
return result_string return result_string
async def aioget(self, url): async def aioget(self, url):
@ -73,11 +79,12 @@ class Common(Cog):
self.bot.log.info(f"Data from {url}: {text_data}") self.bot.log.info(f"Data from {url}: {text_data}")
return text_data return text_data
else: else:
self.bot.log.error(f"HTTP Error {data.status} " self.bot.log.error(f"HTTP Error {data.status} " "while getting {url}")
"while getting {url}")
except: except:
self.bot.log.error(f"Error while getting {url} " self.bot.log.error(
f"on aiogetbytes: {traceback.format_exc()}") f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}"
)
async def aiogetbytes(self, url): async def aiogetbytes(self, url):
try: try:
@ -87,11 +94,12 @@ class Common(Cog):
self.bot.log.debug(f"Data from {url}: {byte_data}") self.bot.log.debug(f"Data from {url}: {byte_data}")
return byte_data return byte_data
else: else:
self.bot.log.error(f"HTTP Error {data.status} " self.bot.log.error(f"HTTP Error {data.status} " "while getting {url}")
"while getting {url}")
except: except:
self.bot.log.error(f"Error while getting {url} " self.bot.log.error(
f"on aiogetbytes: {traceback.format_exc()}") f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}"
)
async def aiojson(self, url): async def aiojson(self, url):
try: try:
@ -99,18 +107,19 @@ class Common(Cog):
if data.status == 200: if data.status == 200:
text_data = await data.text() text_data = await data.text()
self.bot.log.info(f"Data from {url}: {text_data}") self.bot.log.info(f"Data from {url}: {text_data}")
content_type = data.headers['Content-Type'] content_type = data.headers["Content-Type"]
return await data.json(content_type=content_type) return await data.json(content_type=content_type)
else: else:
self.bot.log.error(f"HTTP Error {data.status} " self.bot.log.error(f"HTTP Error {data.status} " "while getting {url}")
"while getting {url}")
except: except:
self.bot.log.error(f"Error while getting {url} " self.bot.log.error(
f"on aiogetbytes: {traceback.format_exc()}") f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}"
)
def hex_to_int(self, color_hex: str): def hex_to_int(self, color_hex: str):
"""Turns a given hex color into an integer""" """Turns a given hex color into an integer"""
return int("0x" + color_hex.strip('#'), 16) return int("0x" + color_hex.strip("#"), 16)
def escape_message(self, text: str): def escape_message(self, text: str):
"""Escapes unfun stuff from messages""" """Escapes unfun stuff from messages"""
@ -130,10 +139,12 @@ class Common(Cog):
"""Slices a message into multiple messages""" """Slices a message into multiple messages"""
if len(text) > size * self.max_split_length: if len(text) > size * self.max_split_length:
haste_url = await self.haste(text) haste_url = await self.haste(text)
return [f"Message is too long ({len(text)} > " return [
f"{size * self.max_split_length} " f"Message is too long ({len(text)} > "
f"({size} * {self.max_split_length}))" f"{size * self.max_split_length} "
f", go to haste: <{haste_url}>"] f"({size} * {self.max_split_length}))"
f", go to haste: <{haste_url}>"
]
reply_list = [] reply_list = []
size_wo_fix = size - len(prefix) - len(suffix) size_wo_fix = size - len(prefix) - len(suffix)
while len(text) > size_wo_fix: while len(text) > size_wo_fix:
@ -142,28 +153,28 @@ class Common(Cog):
reply_list.append(f"{prefix}{text}{suffix}") reply_list.append(f"{prefix}{text}{suffix}")
return reply_list return reply_list
async def haste(self, text, instance='https://mystb.in/'): async def haste(self, text, instance="https://mystb.in/"):
response = await self.bot.aiosession.post(f"{instance}documents", response = await self.bot.aiosession.post(f"{instance}documents", data=text)
data=text)
if response.status == 200: if response.status == 200:
result_json = await response.json() result_json = await response.json()
return f"{instance}{result_json['key']}" return f"{instance}{result_json['key']}"
else: else:
return f"Error {response.status}: {response.text}" return f"Error {response.status}: {response.text}"
async def async_call_shell(self, shell_command: str, async def async_call_shell(
inc_stdout=True, inc_stderr=True): self, shell_command: str, inc_stdout=True, inc_stderr=True
):
pipe = asyncio.subprocess.PIPE pipe = asyncio.subprocess.PIPE
proc = await asyncio.create_subprocess_shell(str(shell_command), proc = await asyncio.create_subprocess_shell(
stdout=pipe, str(shell_command), stdout=pipe, stderr=pipe
stderr=pipe) )
if not (inc_stdout or inc_stderr): if not (inc_stdout or inc_stderr):
return "??? you set both stdout and stderr to False????" return "??? you set both stdout and stderr to False????"
proc_result = await proc.communicate() proc_result = await proc.communicate()
stdout_str = proc_result[0].decode('utf-8').strip() stdout_str = proc_result[0].decode("utf-8").strip()
stderr_str = proc_result[1].decode('utf-8').strip() stderr_str = proc_result[1].decode("utf-8").strip()
if inc_stdout and not inc_stderr: if inc_stdout and not inc_stderr:
return stdout_str return stdout_str
@ -171,8 +182,7 @@ class Common(Cog):
return stderr_str return stderr_str
if stdout_str and stderr_str: if stdout_str and stderr_str:
return f"stdout:\n\n{stdout_str}\n\n"\ return f"stdout:\n\n{stdout_str}\n\n" f"======\n\nstderr:\n\n{stderr_str}"
f"======\n\nstderr:\n\n{stderr_str}"
elif stdout_str: elif stdout_str:
return f"stdout:\n\n{stdout_str}" return f"stdout:\n\n{stdout_str}"
elif stderr_str: elif stderr_str:

View file

@ -5,17 +5,20 @@ from discord.ext import commands
from discord.ext.commands import Cog from discord.ext.commands import Cog
from helpers.errcodes import * from helpers.errcodes import *
class Err(Cog): class Err(Cog):
"""Everything related to Nintendo 3DS, Wii U and Switch error codes""" """Everything related to Nintendo 3DS, Wii U and Switch error codes"""
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.dds_re = re.compile(r'0\d{2}\-\d{4}') self.dds_re = re.compile(r"0\d{2}\-\d{4}")
self.wiiu_re = re.compile(r'1\d{2}\-\d{4}') self.wiiu_re = re.compile(r"1\d{2}\-\d{4}")
self.switch_re = re.compile(r'2\d{3}\-\d{4}') self.switch_re = re.compile(r"2\d{3}\-\d{4}")
self.no_err_desc = "It seems like your error code is unknown. "\ self.no_err_desc = (
"You can check on Switchbrew for your error code at "\ "It seems like your error code is unknown. "
"<https://switchbrew.org/wiki/Error_codes>" "You can check on Switchbrew for your error code at "
"<https://switchbrew.org/wiki/Error_codes>"
)
self.rickroll = "https://www.youtube.com/watch?v=z3ZiVn5L9vM" self.rickroll = "https://www.youtube.com/watch?v=z3ZiVn5L9vM"
@commands.command(aliases=["3dserr", "3err", "dserr"]) @commands.command(aliases=["3dserr", "3err", "dserr"])
@ -28,9 +31,9 @@ class Err(Cog):
else: else:
err_description = self.no_err_desc err_description = self.no_err_desc
# Make a nice Embed out of it # Make a nice Embed out of it
embed = discord.Embed(title=err, embed = discord.Embed(
url=self.rickroll, title=err, url=self.rickroll, description=err_description
description=err_description) )
embed.set_footer(text="Console: 3DS") embed.set_footer(text="Console: 3DS")
# Send message, crazy # Send message, crazy
@ -47,8 +50,7 @@ class Err(Cog):
level = (rc >> 27) & 0x1F level = (rc >> 27) & 0x1F
embed = discord.Embed(title=f"0x{rc:X}") embed = discord.Embed(title=f"0x{rc:X}")
embed.add_field(name="Module", value=dds_modules.get(mod, mod)) embed.add_field(name="Module", value=dds_modules.get(mod, mod))
embed.add_field(name="Description", embed.add_field(name="Description", value=dds_descriptions.get(desc, desc))
value=dds_descriptions.get(desc, desc))
embed.add_field(name="Summary", value=dds_summaries.get(summ, summ)) embed.add_field(name="Summary", value=dds_summaries.get(summ, summ))
embed.add_field(name="Level", value=dds_levels.get(level, level)) embed.add_field(name="Level", value=dds_levels.get(level, level))
embed.set_footer(text="Console: 3DS") embed.set_footer(text="Console: 3DS")
@ -56,8 +58,10 @@ class Err(Cog):
await ctx.send(embed=embed) await ctx.send(embed=embed)
return return
else: else:
await ctx.send("Unknown Format - This is either " await ctx.send(
"no error code or you made some mistake!") "Unknown Format - This is either "
"no error code or you made some mistake!"
)
@commands.command(aliases=["wiiuserr", "uerr", "wuerr", "mochaerr"]) @commands.command(aliases=["wiiuserr", "uerr", "wuerr", "mochaerr"])
async def wiiuerr(self, ctx, err: str): async def wiiuerr(self, ctx, err: str):
@ -72,9 +76,9 @@ class Err(Cog):
err_description = self.no_err_desc err_description = self.no_err_desc
# Make a nice Embed out of it # Make a nice Embed out of it
embed = discord.Embed(title=err, embed = discord.Embed(
url=self.rickroll, title=err, url=self.rickroll, description=err_description
description=err_description) )
embed.set_footer(text="Console: Wii U") embed.set_footer(text="Console: Wii U")
embed.add_field(name="Module", value=module, inline=True) embed.add_field(name="Module", value=module, inline=True)
embed.add_field(name="Description", value=desc, inline=True) embed.add_field(name="Description", value=desc, inline=True)
@ -82,8 +86,10 @@ class Err(Cog):
# Send message, crazy # Send message, crazy
await ctx.send(embed=embed) await ctx.send(embed=embed)
else: else:
await ctx.send("Unknown Format - This is either " await ctx.send(
"no error code or you made some mistake!") "Unknown Format - This is either "
"no error code or you made some mistake!"
)
@commands.command(aliases=["nxerr", "serr"]) @commands.command(aliases=["nxerr", "serr"])
async def err(self, ctx, err: str): async def err(self, ctx, err: str):
@ -125,19 +131,21 @@ class Err(Cog):
err_description = errcode_range[2] err_description = errcode_range[2]
# Make a nice Embed out of it # Make a nice Embed out of it
embed = discord.Embed(title=f"{str_errcode} / {hex(errcode)}", embed = discord.Embed(
url=self.rickroll, title=f"{str_errcode} / {hex(errcode)}",
description=err_description) url=self.rickroll,
embed.add_field(name="Module", description=err_description,
value=f"{err_module} ({module})", )
inline=True) embed.add_field(
name="Module", value=f"{err_module} ({module})", inline=True
)
embed.add_field(name="Description", value=desc, inline=True) embed.add_field(name="Description", value=desc, inline=True)
if "ban" in err_description: if "ban" in err_description:
embed.set_footer("F to you | Console: Switch") embed.set_footer("F to you | Console: Switch")
else: else:
embed.set_footer(text="Console: Switch") embed.set_footer(text="Console: Switch")
await ctx.send(embed=embed) await ctx.send(embed=embed)
# Special case handling because Nintendo feels like # Special case handling because Nintendo feels like
@ -145,17 +153,17 @@ class Err(Cog):
elif err in switch_game_err: elif err in switch_game_err:
game, desc = switch_game_err[err].split(":") game, desc = switch_game_err[err].split(":")
embed = discord.Embed(title=err, embed = discord.Embed(title=err, url=self.rickroll, description=desc)
url=self.rickroll,
description=desc)
embed.set_footer(text="Console: Switch") embed.set_footer(text="Console: Switch")
embed.add_field(name="Game", value=game, inline=True) embed.add_field(name="Game", value=game, inline=True)
await ctx.send(embed=embed) await ctx.send(embed=embed)
else: else:
await ctx.send("Unknown Format - This is either " await ctx.send(
"no error code or you made some mistake!") "Unknown Format - This is either "
"no error code or you made some mistake!"
)
@commands.command(aliases=["e2h"]) @commands.command(aliases=["e2h"])
async def err2hex(self, ctx, err: str): async def err2hex(self, ctx, err: str):
@ -167,8 +175,9 @@ class Err(Cog):
errcode = (desc << 9) + module errcode = (desc << 9) + module
await ctx.send(hex(errcode)) await ctx.send(hex(errcode))
else: else:
await ctx.send("This doesn't follow the typical" await ctx.send(
" Nintendo Switch 2XXX-XXXX format!") "This doesn't follow the typical" " Nintendo Switch 2XXX-XXXX format!"
)
@commands.command(aliases=["h2e"]) @commands.command(aliases=["h2e"])
async def hex2err(self, ctx, err: str): async def hex2err(self, ctx, err: str):

View file

@ -21,7 +21,9 @@ class ImageManip(Cog):
"""Gives a cox headline""" """Gives a cox headline"""
mention = ctx.author.mention mention = ctx.author.mention
headline = await commands.clean_content(fix_channel_mentions=True).convert(ctx, headline) headline = await commands.clean_content(fix_channel_mentions=True).convert(
ctx, headline
)
in_vice = "assets/motherboardlogo.png" in_vice = "assets/motherboardlogo.png"
in_byjcox = "assets/byjcox.png" in_byjcox = "assets/byjcox.png"
@ -53,7 +55,7 @@ class ImageManip(Cog):
for line in lines: for line in lines:
# Get size of the text by font, create a new image of that size # Get size of the text by font, create a new image of that size
size = f.getsize(line) size = f.getsize(line)
txt = PIL.Image.new('L', size) txt = PIL.Image.new("L", size)
# Draw the text # Draw the text
d = PIL.ImageDraw.Draw(txt) d = PIL.ImageDraw.Draw(txt)
@ -61,8 +63,9 @@ class ImageManip(Cog):
# Paste the text into the base image # Paste the text into the base image
w = txt.rotate(0, expand=1) w = txt.rotate(0, expand=1)
im.paste(PIL.ImageOps.colorize(w, (0, 0, 0), im.paste(
(0, 0, 0)), (horipos, vertpos), w) PIL.ImageOps.colorize(w, (0, 0, 0), (0, 0, 0)), (horipos, vertpos), w
)
# Calculate position on next line # Calculate position on next line
vertpos += size[1] + line_spacing vertpos += size[1] + line_spacing
@ -77,8 +80,7 @@ class ImageManip(Cog):
# Save image # Save image
out_filename = f"/tmp/{ctx.message.id}-out.png" out_filename = f"/tmp/{ctx.message.id}-out.png"
im.save(out_filename, quality=100, optimize=True) im.save(out_filename, quality=100, optimize=True)
await ctx.send(content=f"{mention}: Enjoy.", await ctx.send(content=f"{mention}: Enjoy.", file=discord.File(out_filename))
file=discord.File(out_filename))
def setup(bot): def setup(bot):

View file

@ -4,6 +4,7 @@ from helpers.checks import check_if_collaborator
import config import config
import json import json
class Invites(Cog): class Invites(Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@ -15,8 +16,9 @@ class Invites(Cog):
welcome_channel = self.bot.get_channel(config.welcome_channel) welcome_channel = self.bot.get_channel(config.welcome_channel)
author = ctx.message.author author = ctx.message.author
reason = f"Created by {str(author)} ({author.id})" reason = f"Created by {str(author)} ({author.id})"
invite = await welcome_channel.create_invite(max_age = 0, invite = await welcome_channel.create_invite(
max_uses = 1, temporary = True, unique = True, reason = reason) max_age=0, max_uses=1, temporary=True, unique=True, reason=reason
)
with open("data/invites.json", "r") as f: with open("data/invites.json", "r") as f:
invites = json.load(f) invites = json.load(f)
@ -25,7 +27,7 @@ class Invites(Cog):
"uses": 0, "uses": 0,
"url": invite.url, "url": invite.url,
"max_uses": 1, "max_uses": 1,
"code": invite.code "code": invite.code,
} }
with open("data/invites.json", "w") as f: with open("data/invites.json", "w") as f:
@ -35,8 +37,10 @@ class Invites(Cog):
try: try:
await ctx.author.send(f"Created single-use invite {invite.url}") await ctx.author.send(f"Created single-use invite {invite.url}")
except discord.errors.Forbidden: except discord.errors.Forbidden:
await ctx.send(f"{ctx.author.mention} I could not send you the \ await ctx.send(
invite. Send me a DM so I can reply to you.") f"{ctx.author.mention} I could not send you the \
invite. Send me a DM so I can reply to you."
)
def setup(bot): def setup(bot):

View file

@ -1,6 +1,7 @@
from discord.ext import commands from discord.ext import commands
from discord.ext.commands import Cog from discord.ext.commands import Cog
class Legacy(Cog): class Legacy(Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
@ -8,22 +9,28 @@ class Legacy(Cog):
@commands.command(hidden=True, aliases=["removehacker"]) @commands.command(hidden=True, aliases=["removehacker"])
async def probate(self, ctx): async def probate(self, ctx):
"""Use .revoke <user> <role>""" """Use .revoke <user> <role>"""
await ctx.send("This command was replaced with `.revoke <user> <role>`" await ctx.send(
" on Robocop-NG, please use that instead.") "This command was replaced with `.revoke <user> <role>`"
" on Robocop-NG, please use that instead."
)
@commands.command(hidden=True) @commands.command(hidden=True)
async def softlock(self, ctx): async def softlock(self, ctx):
"""Use .lock True""" """Use .lock True"""
await ctx.send("This command was replaced with `.lock True`" await ctx.send(
" on Robocop-NG, please use that instead.\n" "This command was replaced with `.lock True`"
"Also... good luck, and sorry for taking your time. " " on Robocop-NG, please use that instead.\n"
"Lockdown rarely means anything good.") "Also... good luck, and sorry for taking your time. "
"Lockdown rarely means anything good."
)
@commands.command(hidden=True, aliases=["addhacker"]) @commands.command(hidden=True, aliases=["addhacker"])
async def unprobate(self, ctx): async def unprobate(self, ctx):
"""Use .approve <user> <role>""" """Use .approve <user> <role>"""
await ctx.send("This command was replaced with `.approve <user> <role>`" await ctx.send(
" on Robocop-NG, please use that instead.") "This command was replaced with `.approve <user> <role>`"
" on Robocop-NG, please use that instead."
)
def setup(bot): def setup(bot):

View file

@ -25,64 +25,74 @@ class Links(Cog):
@commands.command(hidden=True, aliases=["xyproblem"]) @commands.command(hidden=True, aliases=["xyproblem"])
async def xy(self, ctx): async def xy(self, ctx):
"""Link to the "What is the XY problem?" post from SE""" """Link to the "What is the XY problem?" post from SE"""
await ctx.send("<https://meta.stackexchange.com/q/66377/285481>\n\n" await ctx.send(
"TL;DR: It's asking about your attempted solution " "<https://meta.stackexchange.com/q/66377/285481>\n\n"
"rather than your actual problem.\n" "TL;DR: It's asking about your attempted solution "
"It's perfectly okay to want to learn about a " "rather than your actual problem.\n"
"solution, but please be clear about your intentions " "It's perfectly okay to want to learn about a "
"if you're not actually trying to solve a problem.") "solution, but please be clear about your intentions "
"if you're not actually trying to solve a problem."
)
@commands.command(hidden=True, aliases=["guides", "link"]) @commands.command(hidden=True, aliases=["guides", "link"])
async def guide(self, ctx): async def guide(self, ctx):
"""Link to the guide(s)""" """Link to the guide(s)"""
await ctx.send("**Generic starter guides:**\n" await ctx.send(
"Nintendo Homebrew's Guide: " "**Generic starter guides:**\n"
"<https://nh-server.github.io/switch-guide/>\n" "Nintendo Homebrew's Guide: "
# "AtlasNX's Guide: " "<https://nh-server.github.io/switch-guide/>\n"
# "<https://switch.homebrew.guide>\n" # "AtlasNX's Guide: "
# "Pegaswitch Guide: <https://switch.hacks.guide/> " # "<https://switch.homebrew.guide>\n"
# "(outdated for anything but Pegaswitch/3.0.0)\n" # "Pegaswitch Guide: <https://switch.hacks.guide/> "
"\n**Specific guides:**\n" # "(outdated for anything but Pegaswitch/3.0.0)\n"
"Manually Updating/Downgrading (with HOS): " "\n**Specific guides:**\n"
"<https://switch.homebrew.guide/usingcfw/manualupgrade>\n" "Manually Updating/Downgrading (with HOS): "
"Manually Repairing/Downgrading (without HOS): " "<https://switch.homebrew.guide/usingcfw/manualupgrade>\n"
"<https://switch.homebrew.guide/usingcfw/manualchoiupgrade>\n" "Manually Repairing/Downgrading (without HOS): "
"How to set up a Homebrew development environment: " "<https://switch.homebrew.guide/usingcfw/manualchoiupgrade>\n"
"<https://devkitpro.org/wiki/Getting_Started>\n" "How to set up a Homebrew development environment: "
"Getting full RAM in homebrew without NSPs: " "<https://devkitpro.org/wiki/Getting_Started>\n"
"as of Atmosphere 0.8.6, hold R while opening any game.\n" "Getting full RAM in homebrew without NSPs: "
"Check if a switch is vulnerable to RCM through serial: " "as of Atmosphere 0.8.6, hold R while opening any game.\n"
"<https://akdm.github.io/ssnc/checker/>") "Check if a switch is vulnerable to RCM through serial: "
"<https://akdm.github.io/ssnc/checker/>"
)
@commands.command() @commands.command()
async def source(self, ctx): async def source(self, ctx):
"""Gives link to source code.""" """Gives link to source code."""
await ctx.send(f"You can find my source at {config.source_url}. " await ctx.send(
"Serious PRs and issues welcome!") f"You can find my source at {config.source_url}. "
"Serious PRs and issues welcome!"
)
@commands.command() @commands.command()
async def rules(self, ctx, *, targetuser: discord.Member = None): async def rules(self, ctx, *, targetuser: discord.Member = None):
"""Post a link to the Rules""" """Post a link to the Rules"""
if not targetuser: if not targetuser:
targetuser = ctx.author targetuser = ctx.author
await ctx.send(f"{targetuser.mention}: A link to the rules " await ctx.send(
f"can be found here: {config.rules_url}") f"{targetuser.mention}: A link to the rules "
f"can be found here: {config.rules_url}"
)
@commands.command() @commands.command()
async def community(self, ctx, *, targetuser: discord.Member = None): async def community(self, ctx, *, targetuser: discord.Member = None):
"""Post a link to the community section of the rules""" """Post a link to the community section of the rules"""
if not targetuser: if not targetuser:
targetuser = ctx.author targetuser = ctx.author
await ctx.send(f"{targetuser.mention}: " await ctx.send(
"https://reswitched.team/discord/#member-roles-breakdown" f"{targetuser.mention}: "
"\n\n" "https://reswitched.team/discord/#member-roles-breakdown"
"Community role allows access to the set of channels " "\n\n"
"on the community category (#off-topic, " "Community role allows access to the set of channels "
"#homebrew-development, #switch-hacking-general etc)." "on the community category (#off-topic, "
"\n\n" "#homebrew-development, #switch-hacking-general etc)."
"What you need to get the role is to be around, " "\n\n"
"be helpful and nice to people and " "What you need to get the role is to be around, "
"show an understanding of rules.") "be helpful and nice to people and "
"show an understanding of rules."
)
def setup(bot): def setup(bot):

View file

@ -20,38 +20,38 @@ class Lists(Cog):
return any(r.id in config.staff_role_ids for r in target.roles) return any(r.id in config.staff_role_ids for r in target.roles)
def is_edit(self, emoji): def is_edit(self, emoji):
return str(emoji)[0] == u"" or str(emoji)[0] == u"📝" return str(emoji)[0] == "" or str(emoji)[0] == "📝"
def is_delete(self, emoji): def is_delete(self, emoji):
return str(emoji)[0] == u"" or str(emoji)[0] == u"" return str(emoji)[0] == "" or str(emoji)[0] == ""
def is_recycle(self, emoji): def is_recycle(self, emoji):
return str(emoji)[0] == u"" return str(emoji)[0] == ""
def is_insert_above(self, emoji): def is_insert_above(self, emoji):
return str(emoji)[0] == u"⤴️" or str(emoji)[0] == u"" return str(emoji)[0] == "⤴️" or str(emoji)[0] == ""
def is_insert_below(self, emoji): def is_insert_below(self, emoji):
return str(emoji)[0] == u"⤵️" or str(emoji)[0] == u"" return str(emoji)[0] == "⤵️" or str(emoji)[0] == ""
def is_reaction_valid(self, reaction): def is_reaction_valid(self, reaction):
allowed_reactions = [ allowed_reactions = [
u"", "",
u"📝", "📝",
u"", "",
u"", "",
u"", "",
u"⤴️", "⤴️",
u"", "",
u"", "",
u"⤵️", "⤵️",
] ]
return str(reaction.emoji)[0] in allowed_reactions return str(reaction.emoji)[0] in allowed_reactions
async def find_reactions(self, user_id, channel_id, limit = None): async def find_reactions(self, user_id, channel_id, limit=None):
reactions = [] reactions = []
channel = self.bot.get_channel(channel_id) channel = self.bot.get_channel(channel_id)
async for message in channel.history(limit = limit): async for message in channel.history(limit=limit):
if len(message.reactions) == 0: if len(message.reactions) == 0:
continue continue
@ -63,9 +63,11 @@ class Lists(Cog):
return reactions return reactions
def create_log_message(self, emoji, action, user, channel, reason = ""): def create_log_message(self, emoji, action, user, channel, reason=""):
msg = f"{emoji} **{action}** \n"\ msg = (
f"{emoji} **{action}** \n"
f"from {self.bot.escape_message(user.name)} ({user.id}), in {channel.mention}" f"from {self.bot.escape_message(user.name)} ({user.id}), in {channel.mention}"
)
if reason != "": if reason != "":
msg += f":\n`{reason}`" msg += f":\n`{reason}`"
@ -84,11 +86,11 @@ class Lists(Cog):
file_message = await files_channel.fetch_message(int(field.value)) file_message = await files_channel.fetch_message(int(field.value))
await file_message.delete() await file_message.delete()
await message.edit(embed = None) await message.edit(embed=None)
# Commands # Commands
@commands.command(aliases = ["list"]) @commands.command(aliases=["list"])
async def listitem(self, ctx, channel: discord.TextChannel, number: int): async def listitem(self, ctx, channel: discord.TextChannel, number: int):
"""Link to a specific list item.""" """Link to a specific list item."""
if number <= 0: if number <= 0:
@ -100,20 +102,17 @@ class Lists(Cog):
return return
counter = 0 counter = 0
async for message in channel.history(limit = None, oldest_first = True): async for message in channel.history(limit=None, oldest_first=True):
if message.content.strip(): if message.content.strip():
counter += 1 counter += 1
if counter == number: if counter == number:
embed = discord.Embed( embed = discord.Embed(
title = f"Item #{number} in #{channel.name}", title=f"Item #{number} in #{channel.name}",
description = message.content, description=message.content,
url = message.jump_url url=message.jump_url,
)
await ctx.send(
content = "",
embed = embed
) )
await ctx.send(content="", embed=embed)
return return
await ctx.send(f"Unable to find item #{number} in {channel.mention}.") await ctx.send(f"Unable to find item #{number} in {channel.mention}.")
@ -133,8 +132,13 @@ class Lists(Cog):
member = channel.guild.get_member(payload.user_id) member = channel.guild.get_member(payload.user_id)
user = self.bot.get_user(payload.user_id) user = self.bot.get_user(payload.user_id)
reaction = next( reaction = next(
(reaction for reaction in message.reactions (
if str(reaction.emoji) == str(payload.emoji)), None) reaction
for reaction in message.reactions
if str(reaction.emoji) == str(payload.emoji)
),
None,
)
if reaction is None: if reaction is None:
return return
@ -155,8 +159,9 @@ class Lists(Cog):
# Remove all other reactions from user in this channel. # Remove all other reactions from user in this channel.
for r in await self.find_reactions(payload.user_id, payload.channel_id): for r in await self.find_reactions(payload.user_id, payload.channel_id):
if r.message.id != message.id or (r.message.id == message.id and if r.message.id != message.id or (
str(r.emoji) != str(reaction.emoji)): r.message.id == message.id and str(r.emoji) != str(reaction.emoji)
):
await r.remove(user) await r.remove(user)
# When editing we want to provide the user a copy of the raw text. # When editing we want to provide the user a copy of the raw text.
@ -164,17 +169,16 @@ class Lists(Cog):
files_channel = self.bot.get_channel(config.list_files_channel) files_channel = self.bot.get_channel(config.list_files_channel)
file = discord.File( file = discord.File(
io.BytesIO(message.content.encode("utf-8")), io.BytesIO(message.content.encode("utf-8")),
filename = f"{message.id}.txt") filename=f"{message.id}.txt",
file_message = await files_channel.send(file = file) )
file_message = await files_channel.send(file=file)
embed = discord.Embed( embed = discord.Embed(
title = "Click here to get the raw text to modify.", title="Click here to get the raw text to modify.",
url = f"{file_message.attachments[0].url}?") url=f"{file_message.attachments[0].url}?",
embed.add_field( )
name = "Message ID", embed.add_field(name="Message ID", value=file_message.id, inline=False)
value = file_message.id, await message.edit(embed=embed)
inline = False)
await message.edit(embed = embed)
@Cog.listener() @Cog.listener()
async def on_raw_reaction_remove(self, payload): async def on_raw_reaction_remove(self, payload):
@ -221,9 +225,16 @@ class Lists(Cog):
attachment_data = None attachment_data = None
if len(message.attachments) != 0: if len(message.attachments) != 0:
# Lists will only reupload the first image. # Lists will only reupload the first image.
attachment = next((a for a in message.attachments if attachment = next(
a.filename.endswith(".png") or a.filename.endswith(".jpg") or (
a.filename.endswith(".jpeg")), None) a
for a in message.attachments
if a.filename.endswith(".png")
or a.filename.endswith(".jpg")
or a.filename.endswith(".jpeg")
),
None,
)
if attachment is not None: if attachment is not None:
attachment_filename = attachment.filename attachment_filename = attachment.filename
attachment_data = await attachment.read() attachment_data = await attachment.read()
@ -237,16 +248,18 @@ class Lists(Cog):
if len(reactions) != 1: if len(reactions) != 1:
if attachment_filename is not None and attachment_data is not None: if attachment_filename is not None and attachment_data is not None:
file = discord.File( file = discord.File(
io.BytesIO(attachment_data), io.BytesIO(attachment_data), filename=attachment_filename
filename = attachment_filename) )
await channel.send(content = content, file = file) await channel.send(content=content, file=file)
else: else:
await channel.send(content) await channel.send(content)
for reaction in reactions: for reaction in reactions:
await reaction.remove(user) await reaction.remove(user)
await log_channel.send(self.create_log_message("💬", "List item added:", user, channel)) await log_channel.send(
self.create_log_message("💬", "List item added:", user, channel)
)
return return
targeted_reaction = reactions[0] targeted_reaction = reactions[0]
@ -255,47 +268,67 @@ class Lists(Cog):
if self.is_edit(targeted_reaction): if self.is_edit(targeted_reaction):
if config.list_files_channel != 0: if config.list_files_channel != 0:
await self.clean_up_raw_text_file_message(targeted_message) await self.clean_up_raw_text_file_message(targeted_message)
await targeted_message.edit(content = content) await targeted_message.edit(content=content)
await targeted_reaction.remove(user) await targeted_reaction.remove(user)
await log_channel.send(self.create_log_message("📝", "List item edited:", user, channel)) await log_channel.send(
self.create_log_message("📝", "List item edited:", user, channel)
)
elif self.is_delete(targeted_reaction): elif self.is_delete(targeted_reaction):
await targeted_message.delete() await targeted_message.delete()
await log_channel.send(self.create_log_message("", "List item deleted:", user, channel, content)) await log_channel.send(
self.create_log_message(
"", "List item deleted:", user, channel, content
)
)
elif self.is_recycle(targeted_reaction): elif self.is_recycle(targeted_reaction):
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten() messages = await channel.history(
await channel.purge(limit = len(messages) + 1, bulk = True) limit=None, after=targeted_message, oldest_first=True
).flatten()
await channel.purge(limit=len(messages) + 1, bulk=True)
await channel.send(targeted_message.content) await channel.send(targeted_message.content)
for message in messages: for message in messages:
await channel.send(message.content) await channel.send(message.content)
await log_channel.send(self.create_log_message("", "List item recycled:", user, channel, content)) await log_channel.send(
self.create_log_message(
"", "List item recycled:", user, channel, content
)
)
elif self.is_insert_above(targeted_reaction): elif self.is_insert_above(targeted_reaction):
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten() messages = await channel.history(
await channel.purge(limit = len(messages) + 1, bulk = True) limit=None, after=targeted_message, oldest_first=True
).flatten()
await channel.purge(limit=len(messages) + 1, bulk=True)
await channel.send(content) await channel.send(content)
await channel.send(targeted_message.content) await channel.send(targeted_message.content)
for message in messages: for message in messages:
await channel.send(message.content) await channel.send(message.content)
await log_channel.send(self.create_log_message("💬", "List item added:", user, channel)) await log_channel.send(
self.create_log_message("💬", "List item added:", user, channel)
)
elif self.is_insert_below(targeted_reaction): elif self.is_insert_below(targeted_reaction):
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten() messages = await channel.history(
await channel.purge(limit = len(messages) + 1, bulk = True) limit=None, after=targeted_message, oldest_first=True
).flatten()
await channel.purge(limit=len(messages) + 1, bulk=True)
await channel.send(targeted_message.content) await channel.send(targeted_message.content)
await channel.send(content) await channel.send(content)
for message in messages: for message in messages:
await channel.send(message.content) await channel.send(message.content)
await log_channel.send(self.create_log_message("💬", "List item added:", user, channel)) await log_channel.send(
self.create_log_message("💬", "List item added:", user, channel)
)
def setup(bot): def setup(bot):

View file

@ -4,19 +4,21 @@ import config
import discord import discord
from helpers.checks import check_if_staff from helpers.checks import check_if_staff
class Lockdown(Cog): class Lockdown(Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
async def set_sendmessage(self, channel: discord.TextChannel, async def set_sendmessage(
role, allow_send, issuer): self, channel: discord.TextChannel, role, allow_send, issuer
):
try: try:
roleobj = channel.guild.get_role(role) roleobj = channel.guild.get_role(role)
overrides = channel.overwrites_for(roleobj) overrides = channel.overwrites_for(roleobj)
overrides.send_messages = allow_send overrides.send_messages = allow_send
await channel.set_permissions(roleobj, await channel.set_permissions(
overwrite=overrides, roleobj, overwrite=overrides, reason=str(issuer)
reason=str(issuer)) )
except: except:
pass pass
@ -27,8 +29,7 @@ class Lockdown(Cog):
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command() @commands.command()
async def lock(self, ctx, channel: discord.TextChannel = None, async def lock(self, ctx, channel: discord.TextChannel = None, soft: bool = False):
soft: bool = False):
"""Prevents people from speaking in a channel, staff only. """Prevents people from speaking in a channel, staff only.
Defaults to current channel.""" Defaults to current channel."""
@ -50,14 +51,18 @@ class Lockdown(Cog):
public_msg = "🔒 Channel locked down. " public_msg = "🔒 Channel locked down. "
if not soft: if not soft:
public_msg += "Only staff members may speak. "\ public_msg += (
"Do not bring the topic to other channels or risk "\ "Only staff members may speak. "
"disciplinary actions." "Do not bring the topic to other channels or risk "
"disciplinary actions."
)
await ctx.send(public_msg) await ctx.send(public_msg)
safe_name = await commands.clean_content().convert(ctx, str(ctx.author)) safe_name = await commands.clean_content().convert(ctx, str(ctx.author))
msg = f"🔒 **Lockdown**: {ctx.channel.mention} by {ctx.author.mention} "\ msg = (
f"| {safe_name}" f"🔒 **Lockdown**: {ctx.channel.mention} by {ctx.author.mention} "
f"| {safe_name}"
)
await log_channel.send(msg) await log_channel.send(msg)
@commands.guild_only() @commands.guild_only()
@ -83,8 +88,10 @@ class Lockdown(Cog):
safe_name = await commands.clean_content().convert(ctx, str(ctx.author)) safe_name = await commands.clean_content().convert(ctx, str(ctx.author))
await ctx.send("🔓 Channel unlocked.") await ctx.send("🔓 Channel unlocked.")
msg = f"🔓 **Unlock**: {ctx.channel.mention} by {ctx.author.mention} "\ msg = (
f"| {safe_name}" f"🔓 **Unlock**: {ctx.channel.mention} by {ctx.author.mention} "
f"| {safe_name}"
)
await log_channel.send(msg) await log_channel.send(msg)

View file

@ -14,21 +14,22 @@ class Logs(Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
self.invite_re = re.compile(r"((discord\.gg|discordapp\.com/" self.invite_re = re.compile(
r"+invite)/+[a-zA-Z0-9-]+)", r"((discord\.gg|discordapp\.com/" r"+invite)/+[a-zA-Z0-9-]+)", re.IGNORECASE
re.IGNORECASE) )
self.name_re = re.compile(r"[a-zA-Z0-9].*") self.name_re = re.compile(r"[a-zA-Z0-9].*")
self.clean_re = re.compile(r'[^a-zA-Z0-9_ ]+', re.UNICODE) self.clean_re = re.compile(r"[^a-zA-Z0-9_ ]+", re.UNICODE)
# All lower case, no spaces, nothing non-alphanumeric # All lower case, no spaces, nothing non-alphanumeric
susp_hellgex = "|".join([r"\W*".join(list(word)) for susp_hellgex = "|".join(
word in config.suspect_words]) [r"\W*".join(list(word)) for word in config.suspect_words]
)
self.susp_hellgex = re.compile(susp_hellgex, re.IGNORECASE) self.susp_hellgex = re.compile(susp_hellgex, re.IGNORECASE)
@Cog.listener() @Cog.listener()
async def on_member_join(self, member): async def on_member_join(self, member):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
if (member.guild.id not in config.guild_whitelist): if member.guild.id not in config.guild_whitelist:
return return
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
@ -48,7 +49,7 @@ class Logs(Cog):
"uses": 0, "uses": 0,
"url": invite.url, "url": invite.url,
"max_uses": invite.max_uses, "max_uses": invite.max_uses,
"code": invite.code "code": invite.code,
} }
probable_invites_used = [] probable_invites_used = []
@ -87,31 +88,39 @@ class Logs(Cog):
age = member.joined_at - member.created_at age = member.joined_at - member.created_at
if age < config.min_age: if age < config.min_age:
try: try:
await member.send(f"Your account is too new to " await member.send(
f"join {member.guild.name}." f"Your account is too new to "
" Please try again later.") f"join {member.guild.name}."
" Please try again later."
)
sent = True sent = True
except discord.errors.Forbidden: except discord.errors.Forbidden:
sent = False sent = False
await member.kick(reason="Too new") await member.kick(reason="Too new")
msg = f"🚨 **Account too new**: {member.mention} | "\ msg = (
f"{escaped_name}\n"\ f"🚨 **Account too new**: {member.mention} | "
f"🗓 __Creation__: {member.created_at}\n"\ f"{escaped_name}\n"
f"🕓 Account age: {age}\n"\ f"🗓 __Creation__: {member.created_at}\n"
f"✉ Joined with: {invite_used}\n"\ f"🕓 Account age: {age}\n"
f"🏷 __User ID__: {member.id}" f"✉ Joined with: {invite_used}\n"
f"🏷 __User ID__: {member.id}"
)
if not sent: if not sent:
msg += "\nThe user has disabled direct messages, "\ msg += (
"so the reason was not sent." "\nThe user has disabled direct messages, "
"so the reason was not sent."
)
await log_channel.send(msg) await log_channel.send(msg)
return return
msg = f"✅ **Join**: {member.mention} | "\ msg = (
f"{escaped_name}\n"\ f"✅ **Join**: {member.mention} | "
f"🗓 __Creation__: {member.created_at}\n"\ f"{escaped_name}\n"
f"🕓 Account age: {age}\n"\ f"🗓 __Creation__: {member.created_at}\n"
f"✉ Joined with: {invite_used}\n"\ f"🕓 Account age: {age}\n"
f"🏷 __User ID__: {member.id}" f"✉ Joined with: {invite_used}\n"
f"🏷 __User ID__: {member.id}"
)
# Handles user restrictions # Handles user restrictions
# Basically, gives back muted role to users that leave with it. # Basically, gives back muted role to users that leave with it.
@ -126,13 +135,16 @@ class Logs(Cog):
if len(warns[str(member.id)]["warns"]) == 0: if len(warns[str(member.id)]["warns"]) == 0:
await log_channel.send(msg) await log_channel.send(msg)
else: else:
embed = discord.Embed(color=discord.Color.dark_red(), embed = discord.Embed(
title=f"Warns for {escaped_name}") color=discord.Color.dark_red(), title=f"Warns for {escaped_name}"
)
embed.set_thumbnail(url=member.avatar_url) embed.set_thumbnail(url=member.avatar_url)
for idx, warn in enumerate(warns[str(member.id)]["warns"]): for idx, warn in enumerate(warns[str(member.id)]["warns"]):
embed.add_field(name=f"{idx + 1}: {warn['timestamp']}", embed.add_field(
value=f"Issuer: {warn['issuer_name']}" name=f"{idx + 1}: {warn['timestamp']}",
f"\nReason: {warn['reason']}") value=f"Issuer: {warn['issuer_name']}"
f"\nReason: {warn['reason']}",
)
await log_channel.send(msg, embed=embed) await log_channel.send(msg, embed=embed)
except KeyError: # if the user is not in the file except KeyError: # if the user is not in the file
await log_channel.send(msg) await log_channel.send(msg)
@ -145,9 +157,11 @@ class Logs(Cog):
return return
alert = False alert = False
cleancont = self.clean_re.sub('', message.content).lower() cleancont = self.clean_re.sub("", message.content).lower()
msg = f"🚨 Suspicious message by {message.author.mention} "\ msg = (
f"({message.author.id}):" f"🚨 Suspicious message by {message.author.mention} "
f"({message.author.id}):"
)
invites = self.invite_re.findall(message.content) invites = self.invite_re.findall(message.content)
for invite in invites: for invite in invites:
@ -155,8 +169,9 @@ class Logs(Cog):
alert = True alert = True
for susp_word in config.suspect_words: for susp_word in config.suspect_words:
if susp_word in cleancont and\ if susp_word in cleancont and not any(
not any(ok_word in cleancont for ok_word in config.suspect_ignored_words): ok_word in cleancont for ok_word in config.suspect_ignored_words
):
msg += f"\n- Contains suspicious word: `{susp_word}`" msg += f"\n- Contains suspicious word: `{susp_word}`"
alert = True alert = True
@ -166,13 +181,15 @@ class Logs(Cog):
# Bad Code :tm:, blame retr0id # Bad Code :tm:, blame retr0id
message_clean = message.content.replace("*", "").replace("_", "") message_clean = message.content.replace("*", "").replace("_", "")
regd = self.susp_hellgex.sub(lambda w: "**{}**".format(w.group(0)), regd = self.susp_hellgex.sub(
message_clean) lambda w: "**{}**".format(w.group(0)), message_clean
)
# Show a message embed # Show a message embed
embed = discord.Embed(description=regd) embed = discord.Embed(description=regd)
embed.set_author(name=message.author.display_name, embed.set_author(
icon_url=message.author.avatar_url) name=message.author.display_name, icon_url=message.author.avatar_url
)
await spy_channel.send(msg, embed=embed) await spy_channel.send(msg, embed=embed)
@ -181,8 +198,9 @@ class Logs(Cog):
if compliant: if compliant:
return return
msg = f"R11 violating name by {message.author.mention} "\ msg = (
f"({message.author.id})." f"R11 violating name by {message.author.mention} " f"({message.author.id})."
)
spy_channel = self.bot.get_channel(config.spylog_channel) spy_channel = self.bot.get_channel(config.spylog_channel)
await spy_channel.send(msg) await spy_channel.send(msg)
@ -209,10 +227,12 @@ class Logs(Cog):
await self.do_spy(after) await self.do_spy(after)
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
msg = "📝 **Message edit**: \n"\ msg = (
f"from {self.bot.escape_message(after.author.name)} "\ "📝 **Message edit**: \n"
f"({after.author.id}), in {after.channel.mention}:\n"\ f"from {self.bot.escape_message(after.author.name)} "
f"`{before.clean_content}` → `{after.clean_content}`" f"({after.author.id}), in {after.channel.mention}:\n"
f"`{before.clean_content}` → `{after.clean_content}`"
)
# If resulting message is too long, upload to hastebin # If resulting message is too long, upload to hastebin
if len(msg) > 2000: if len(msg) > 2000:
@ -228,10 +248,12 @@ class Logs(Cog):
return return
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
msg = "🗑️ **Message delete**: \n"\ msg = (
f"from {self.bot.escape_message(message.author.name)} "\ "🗑️ **Message delete**: \n"
f"({message.author.id}), in {message.channel.mention}:\n"\ f"from {self.bot.escape_message(message.author.name)} "
f"`{message.clean_content}`" f"({message.author.id}), in {message.channel.mention}:\n"
f"`{message.clean_content}`"
)
# If resulting message is too long, upload to hastebin # If resulting message is too long, upload to hastebin
if len(msg) > 2000: if len(msg) > 2000:
@ -244,39 +266,45 @@ class Logs(Cog):
async def on_member_remove(self, member): async def on_member_remove(self, member):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
if (member.guild.id not in config.guild_whitelist): if member.guild.id not in config.guild_whitelist:
return return
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
msg = f"⬅️ **Leave**: {member.mention} | "\ msg = (
f"{self.bot.escape_message(member)}\n"\ f"⬅️ **Leave**: {member.mention} | "
f"🏷 __User ID__: {member.id}" f"{self.bot.escape_message(member)}\n"
f"🏷 __User ID__: {member.id}"
)
await log_channel.send(msg) await log_channel.send(msg)
@Cog.listener() @Cog.listener()
async def on_member_ban(self, guild, member): async def on_member_ban(self, guild, member):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
if (guild.id not in config.guild_whitelist): if guild.id not in config.guild_whitelist:
return return
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
msg = f"⛔ **Ban**: {member.mention} | "\ msg = (
f"{self.bot.escape_message(member)}\n"\ f"⛔ **Ban**: {member.mention} | "
f"🏷 __User ID__: {member.id}" f"{self.bot.escape_message(member)}\n"
f"🏷 __User ID__: {member.id}"
)
await log_channel.send(msg) await log_channel.send(msg)
@Cog.listener() @Cog.listener()
async def on_member_unban(self, guild, user): async def on_member_unban(self, guild, user):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
if (guild.id not in config.guild_whitelist): if guild.id not in config.guild_whitelist:
return return
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
msg = f"⚠️ **Unban**: {user.mention} | "\ msg = (
f"{self.bot.escape_message(user)}\n"\ f"⚠️ **Unban**: {user.mention} | "
f"🏷 __User ID__: {user.id}" f"{self.bot.escape_message(user)}\n"
f"🏷 __User ID__: {user.id}"
)
# if user.id in self.bot.timebans: # if user.id in self.bot.timebans:
# msg += "\nTimeban removed." # msg += "\nTimeban removed."
# self.bot.timebans.pop(user.id) # self.bot.timebans.pop(user.id)
@ -292,7 +320,7 @@ class Logs(Cog):
async def on_member_update(self, member_before, member_after): async def on_member_update(self, member_before, member_after):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
if (member_after.guild.id not in config.guild_whitelist): if member_after.guild.id not in config.guild_whitelist:
return return
msg = "" msg = ""
@ -324,9 +352,11 @@ class Logs(Cog):
msg += ", ".join(roles) msg += ", ".join(roles)
if member_before.name != member_after.name: if member_before.name != member_after.name:
msg += "\n📝 __Username change__: "\ msg += (
f"{self.bot.escape_message(member_before)}"\ "\n📝 __Username change__: "
f"{self.bot.escape_message(member_after)}" f"{self.bot.escape_message(member_before)}"
f"{self.bot.escape_message(member_after)}"
)
if member_before.nick != member_after.nick: if member_before.nick != member_after.nick:
if not member_before.nick: if not member_before.nick:
msg += "\n🏷 __Nickname addition__" msg += "\n🏷 __Nickname addition__"
@ -334,11 +364,15 @@ class Logs(Cog):
msg += "\n🏷 __Nickname removal__" msg += "\n🏷 __Nickname removal__"
else: else:
msg += "\n🏷 __Nickname change__" msg += "\n🏷 __Nickname change__"
msg += f": {self.bot.escape_message(member_before.nick)}"\ msg += (
f"{self.bot.escape_message(member_after.nick)}" f": {self.bot.escape_message(member_before.nick)}"
f"{self.bot.escape_message(member_after.nick)}"
)
if msg: if msg:
msg = f" **Member update**: {member_after.mention} | "\ msg = (
f"{self.bot.escape_message(member_after)}{msg}" f" **Member update**: {member_after.mention} | "
f"{self.bot.escape_message(member_after)}{msg}"
)
await log_channel.send(msg) await log_channel.send(msg)

View file

@ -31,9 +31,11 @@ class Meme(Cog):
celsius = random.randint(15, 100) celsius = random.randint(15, 100)
fahrenheit = self.c_to_f(celsius) fahrenheit = self.c_to_f(celsius)
kelvin = self.c_to_k(celsius) kelvin = self.c_to_k(celsius)
await ctx.send(f"{user.mention} warmed." await ctx.send(
f" User is now {celsius}°C " f"{user.mention} warmed."
f"({fahrenheit}°F, {kelvin}K).") f" User is now {celsius}°C "
f"({fahrenheit}°F, {kelvin}K)."
)
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@commands.command(hidden=True, name="chill", aliases=["cold"]) @commands.command(hidden=True, name="chill", aliases=["cold"])
@ -42,9 +44,11 @@ class Meme(Cog):
celsius = random.randint(-50, 15) celsius = random.randint(-50, 15)
fahrenheit = self.c_to_f(celsius) fahrenheit = self.c_to_f(celsius)
kelvin = self.c_to_k(celsius) kelvin = self.c_to_k(celsius)
await ctx.send(f"{user.mention} chilled." await ctx.send(
f" User is now {celsius}°C " f"{user.mention} chilled."
f"({fahrenheit}°F, {kelvin}K).") f" User is now {celsius}°C "
f"({fahrenheit}°F, {kelvin}K)."
)
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@commands.command(hidden=True, aliases=["thank", "reswitchedgold"]) @commands.command(hidden=True, aliases=["thank", "reswitchedgold"])
@ -53,15 +57,18 @@ class Meme(Cog):
await ctx.send(f"{user.mention} gets a :star:, yay!") await ctx.send(f"{user.mention} gets a :star:, yay!")
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@commands.command(hidden=True, aliases=["reswitchedsilver", "silv3r", @commands.command(
"reswitchedsilv3r"]) hidden=True, aliases=["reswitchedsilver", "silv3r", "reswitchedsilv3r"]
)
async def silver(self, ctx, user: discord.Member): async def silver(self, ctx, user: discord.Member):
"""Gives a user ReSwitched Silver™""" """Gives a user ReSwitched Silver™"""
embed = discord.Embed(title="ReSwitched Silver™!", embed = discord.Embed(
description=f"Here's your ReSwitched Silver™," title="ReSwitched Silver™!",
f"{user.mention}!") description=f"Here's your ReSwitched Silver™," f"{user.mention}!",
embed.set_image(url="https://cdn.discordapp.com/emojis/" )
"548623626916724747.png?v=1") embed.set_image(
url="https://cdn.discordapp.com/emojis/" "548623626916724747.png?v=1"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@ -69,9 +76,11 @@ class Meme(Cog):
async def btwiuse(self, ctx): async def btwiuse(self, ctx):
"""btw i use arch""" """btw i use arch"""
uname = platform.uname() uname = platform.uname()
await ctx.send(f"BTW I use {platform.python_implementation()} " await ctx.send(
f"{platform.python_version()} on {uname.system} " f"BTW I use {platform.python_implementation()} "
f"{uname.release}") f"{platform.python_version()} on {uname.system} "
f"{uname.release}"
)
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@commands.command(hidden=True) @commands.command(hidden=True)
@ -95,25 +104,30 @@ class Meme(Cog):
@commands.command(hidden=True, aliases=["outstanding"]) @commands.command(hidden=True, aliases=["outstanding"])
async def outstandingmove(self, ctx): async def outstandingmove(self, ctx):
"""Posts the outstanding move meme""" """Posts the outstanding move meme"""
await ctx.send("https://cdn.discordapp.com/attachments" await ctx.send(
"/371047036348268545/528413677007929344" "https://cdn.discordapp.com/attachments"
"/image0-5.jpg") "/371047036348268545/528413677007929344"
"/image0-5.jpg"
)
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@commands.command(hidden=True) @commands.command(hidden=True)
async def bones(self, ctx): async def bones(self, ctx):
await ctx.send("https://cdn.discordapp.com/emojis/" await ctx.send(
"443501365843591169.png?v=1") "https://cdn.discordapp.com/emojis/" "443501365843591169.png?v=1"
)
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@commands.command(hidden=True) @commands.command(hidden=True)
async def headpat(self, ctx): async def headpat(self, ctx):
await ctx.send("https://cdn.discordapp.com/emojis/" await ctx.send(
"465650811909701642.png?v=1") "https://cdn.discordapp.com/emojis/" "465650811909701642.png?v=1"
)
@commands.check(check_if_staff_or_ot) @commands.check(check_if_staff_or_ot)
@commands.command(hidden=True, aliases=["when", "etawhen", @commands.command(
"emunand", "emummc", "thermosphere"]) hidden=True, aliases=["when", "etawhen", "emunand", "emummc", "thermosphere"]
)
async def eta(self, ctx): async def eta(self, ctx):
await ctx.send("June 15.") await ctx.send("June 15.")
@ -123,11 +137,14 @@ class Meme(Cog):
"""Bams a user owo""" """Bams a user owo"""
if target == ctx.author: if target == ctx.author:
if target.id == 181627658520625152: if target.id == 181627658520625152:
return await ctx.send("https://cdn.discordapp.com/attachments/286612533757083648/403080855402315796/rehedge.PNG") return await ctx.send(
"https://cdn.discordapp.com/attachments/286612533757083648/403080855402315796/rehedge.PNG"
)
return await ctx.send("hedgeberg#7337 is ̶n͢ow b̕&̡.̷ 👍̡") return await ctx.send("hedgeberg#7337 is ̶n͢ow b̕&̡.̷ 👍̡")
elif target == self.bot.user: elif target == self.bot.user:
return await ctx.send(f"I'm sorry {ctx.author.mention}, " return await ctx.send(
"I'm afraid I can't do that.") f"I'm sorry {ctx.author.mention}, " "I'm afraid I can't do that."
)
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
await ctx.send(f"{safe_name} is ̶n͢ow b̕&̡.̷ 👍̡") await ctx.send(f"{safe_name} is ̶n͢ow b̕&̡.̷ 👍̡")
@ -145,8 +162,9 @@ class Meme(Cog):
@commands.command(hidden=True, aliases=["yotld"]) @commands.command(hidden=True, aliases=["yotld"])
async def yearoflinux(self, ctx): async def yearoflinux(self, ctx):
"""Shows the year of Linux on the desktop""" """Shows the year of Linux on the desktop"""
await ctx.send(f"{datetime.datetime.now().year} is the year of " await ctx.send(
"Linux on the Desktop") f"{datetime.datetime.now().year} is the year of " "Linux on the Desktop"
)
def setup(bot): def setup(bot):

View file

@ -25,11 +25,9 @@ class Mod(Cog):
await ctx.send(f"Done!") await ctx.send(f"Done!")
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
log_msg = f"✏️ **Guild Icon Update**: {ctx.author} "\ log_msg = f"✏️ **Guild Icon Update**: {ctx.author} " "changed the guild icon."
"changed the guild icon."
img_filename = url.split("/")[-1].split("#")[0] # hacky img_filename = url.split("/")[-1].split("#")[0] # hacky
img_file = discord.File(io.BytesIO(img_bytes), img_file = discord.File(io.BytesIO(img_bytes), filename=img_filename)
filename=img_filename)
await log_channel.send(log_msg, file=img_file) await log_channel.send(log_msg, file=img_file)
@commands.guild_only() @commands.guild_only()
@ -41,11 +39,13 @@ class Mod(Cog):
if target == ctx.author: if target == ctx.author:
return await ctx.send("You can't do mod actions on yourself.") return await ctx.send("You can't do mod actions on yourself.")
elif target == self.bot.user: elif target == self.bot.user:
return await ctx.send(f"I'm sorry {ctx.author.mention}, " return await ctx.send(
"I'm afraid I can't do that.") f"I'm sorry {ctx.author.mention}, " "I'm afraid I can't do that."
)
elif self.check_if_target_is_staff(target): elif self.check_if_target_is_staff(target):
return await ctx.send("I can't mute this user as " return await ctx.send(
"they're a member of staff.") "I can't mute this user as " "they're a member of staff."
)
userlog(target.id, ctx.author, reason, "mutes", target.name) userlog(target.id, ctx.author, reason, "mutes", target.name)
@ -53,7 +53,7 @@ class Mod(Cog):
dm_message = f"You were muted!" dm_message = f"You were muted!"
if reason: if reason:
dm_message += f" The given reason is: \"{reason}\"." dm_message += f' The given reason is: "{reason}".'
try: try:
await target.send(dm_message) await target.send(dm_message)
@ -66,15 +66,19 @@ class Mod(Cog):
await target.add_roles(mute_role, reason=str(ctx.author)) await target.add_roles(mute_role, reason=str(ctx.author))
chan_message = f"🔇 **Muted**: {ctx.author.mention} muted "\ chan_message = (
f"{target.mention} | {safe_name}\n"\ f"🔇 **Muted**: {ctx.author.mention} muted "
f"🏷 __User ID__: {target.id}\n" f"{target.mention} | {safe_name}\n"
f"🏷 __User ID__: {target.id}\n"
)
if reason: if reason:
chan_message += f"✏️ __Reason__: \"{reason}\"" chan_message += f'✏️ __Reason__: "{reason}"'
else: else:
chan_message += "Please add an explanation below. In the future, "\ chan_message += (
"it is recommended to use `.mute <user> [reason]`"\ "Please add an explanation below. In the future, "
" as the reason is automatically sent to the user." "it is recommended to use `.mute <user> [reason]`"
" as the reason is automatically sent to the user."
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
@ -91,9 +95,11 @@ class Mod(Cog):
mute_role = ctx.guild.get_role(config.mute_role) mute_role = ctx.guild.get_role(config.mute_role)
await target.remove_roles(mute_role, reason=str(ctx.author)) await target.remove_roles(mute_role, reason=str(ctx.author))
chan_message = f"🔈 **Unmuted**: {ctx.author.mention} unmuted "\ chan_message = (
f"{target.mention} | {safe_name}\n"\ f"🔈 **Unmuted**: {ctx.author.mention} unmuted "
f"🏷 __User ID__: {target.id}\n" f"{target.mention} | {safe_name}\n"
f"🏷 __User ID__: {target.id}\n"
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
@ -110,11 +116,13 @@ class Mod(Cog):
if target == ctx.author: if target == ctx.author:
return await ctx.send("You can't do mod actions on yourself.") return await ctx.send("You can't do mod actions on yourself.")
elif target == self.bot.user: elif target == self.bot.user:
return await ctx.send(f"I'm sorry {ctx.author.mention}, " return await ctx.send(
"I'm afraid I can't do that.") f"I'm sorry {ctx.author.mention}, " "I'm afraid I can't do that."
)
elif self.check_if_target_is_staff(target): elif self.check_if_target_is_staff(target):
return await ctx.send("I can't kick this user as " return await ctx.send(
"they're a member of staff.") "I can't kick this user as " "they're a member of staff."
)
userlog(target.id, ctx.author, reason, "kicks", target.name) userlog(target.id, ctx.author, reason, "kicks", target.name)
@ -122,9 +130,11 @@ class Mod(Cog):
dm_message = f"You were kicked from {ctx.guild.name}." dm_message = f"You were kicked from {ctx.guild.name}."
if reason: if reason:
dm_message += f" The given reason is: \"{reason}\"." dm_message += f' The given reason is: "{reason}".'
dm_message += "\n\nYou are able to rejoin the server,"\ dm_message += (
" but please be sure to behave when participating again." "\n\nYou are able to rejoin the server,"
" but please be sure to behave when participating again."
)
try: try:
await target.send(dm_message) await target.send(dm_message)
@ -134,16 +144,20 @@ class Mod(Cog):
pass pass
await target.kick(reason=f"{ctx.author}, reason: {reason}") await target.kick(reason=f"{ctx.author}, reason: {reason}")
chan_message = f"👢 **Kick**: {ctx.author.mention} kicked "\ chan_message = (
f"{target.mention} | {safe_name}\n"\ f"👢 **Kick**: {ctx.author.mention} kicked "
f"🏷 __User ID__: {target.id}\n" f"{target.mention} | {safe_name}\n"
f"🏷 __User ID__: {target.id}\n"
)
if reason: if reason:
chan_message += f"✏️ __Reason__: \"{reason}\"" chan_message += f'✏️ __Reason__: "{reason}"'
else: else:
chan_message += "Please add an explanation below. In the future"\ chan_message += (
", it is recommended to use "\ "Please add an explanation below. In the future"
"`.kick <user> [reason]`"\ ", it is recommended to use "
" as the reason is automatically sent to the user." "`.kick <user> [reason]`"
" as the reason is automatically sent to the user."
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
@ -158,14 +172,18 @@ class Mod(Cog):
# Hedge-proofing the code # Hedge-proofing the code
if target == ctx.author: if target == ctx.author:
if target.id == 181627658520625152: if target.id == 181627658520625152:
return await ctx.send("https://cdn.discordapp.com/attachments/286612533757083648/403080855402315796/rehedge.PNG") return await ctx.send(
"https://cdn.discordapp.com/attachments/286612533757083648/403080855402315796/rehedge.PNG"
)
return await ctx.send("hedgeberg#7337 is now b&. 👍") return await ctx.send("hedgeberg#7337 is now b&. 👍")
elif target == self.bot.user: elif target == self.bot.user:
return await ctx.send(f"I'm sorry {ctx.author.mention}, " return await ctx.send(
"I'm afraid I can't do that.") f"I'm sorry {ctx.author.mention}, " "I'm afraid I can't do that."
)
elif self.check_if_target_is_staff(target): elif self.check_if_target_is_staff(target):
return await ctx.send("I can't ban this user as " return await ctx.send(
"they're a member of staff.") "I can't ban this user as " "they're a member of staff."
)
userlog(target.id, ctx.author, reason, "bans", target.name) userlog(target.id, ctx.author, reason, "bans", target.name)
@ -173,7 +191,7 @@ class Mod(Cog):
dm_message = f"You were banned from {ctx.guild.name}." dm_message = f"You were banned from {ctx.guild.name}."
if reason: if reason:
dm_message += f" The given reason is: \"{reason}\"." dm_message += f' The given reason is: "{reason}".'
dm_message += "\n\nThis ban does not expire." dm_message += "\n\nThis ban does not expire."
try: try:
@ -183,17 +201,22 @@ class Mod(Cog):
# or has DMs disabled # or has DMs disabled
pass pass
await target.ban(reason=f"{ctx.author}, reason: {reason}", await target.ban(
delete_message_days=0) reason=f"{ctx.author}, reason: {reason}", delete_message_days=0
chan_message = f"⛔ **Ban**: {ctx.author.mention} banned "\ )
f"{target.mention} | {safe_name}\n"\ chan_message = (
f"🏷 __User ID__: {target.id}\n" f"⛔ **Ban**: {ctx.author.mention} banned "
f"{target.mention} | {safe_name}\n"
f"🏷 __User ID__: {target.id}\n"
)
if reason: if reason:
chan_message += f"✏️ __Reason__: \"{reason}\"" chan_message += f'✏️ __Reason__: "{reason}"'
else: else:
chan_message += "Please add an explanation below. In the future"\ chan_message += (
", it is recommended to use `.ban <user> [reason]`"\ "Please add an explanation below. In the future"
" as the reason is automatically sent to the user." ", it is recommended to use `.ban <user> [reason]`"
" as the reason is automatically sent to the user."
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
@ -211,28 +234,34 @@ class Mod(Cog):
if target == ctx.author.id: if target == ctx.author.id:
return await ctx.send("You can't do mod actions on yourself.") return await ctx.send("You can't do mod actions on yourself.")
elif target == self.bot.user: elif target == self.bot.user:
return await ctx.send(f"I'm sorry {ctx.author.mention}, " return await ctx.send(
"I'm afraid I can't do that.") f"I'm sorry {ctx.author.mention}, " "I'm afraid I can't do that."
)
elif target_member and self.check_if_target_is_staff(target_member): elif target_member and self.check_if_target_is_staff(target_member):
return await ctx.send("I can't ban this user as " return await ctx.send(
"they're a member of staff.") "I can't ban this user as " "they're a member of staff."
)
userlog(target, ctx.author, reason, "bans", target_user.name) userlog(target, ctx.author, reason, "bans", target_user.name)
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
await ctx.guild.ban(target_user, await ctx.guild.ban(
reason=f"{ctx.author}, reason: {reason}", target_user, reason=f"{ctx.author}, reason: {reason}", delete_message_days=0
delete_message_days=0) )
chan_message = f"⛔ **Hackban**: {ctx.author.mention} banned "\ chan_message = (
f"{target_user.mention} | {safe_name}\n"\ f"⛔ **Hackban**: {ctx.author.mention} banned "
f"🏷 __User ID__: {target}\n" f"{target_user.mention} | {safe_name}\n"
f"🏷 __User ID__: {target}\n"
)
if reason: if reason:
chan_message += f"✏️ __Reason__: \"{reason}\"" chan_message += f'✏️ __Reason__: "{reason}"'
else: else:
chan_message += "Please add an explanation below. In the future"\ chan_message += (
", it is recommended to use "\ "Please add an explanation below. In the future"
"`.hackban <user> [reason]`." ", it is recommended to use "
"`.hackban <user> [reason]`."
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
@ -248,27 +277,34 @@ class Mod(Cog):
if target == ctx.author: if target == ctx.author:
return await ctx.send("You can't do mod actions on yourself.") return await ctx.send("You can't do mod actions on yourself.")
elif target == self.bot.user: elif target == self.bot.user:
return await ctx.send(f"I'm sorry {ctx.author.mention}, " return await ctx.send(
"I'm afraid I can't do that.") f"I'm sorry {ctx.author.mention}, " "I'm afraid I can't do that."
)
elif self.check_if_target_is_staff(target): elif self.check_if_target_is_staff(target):
return await ctx.send("I can't ban this user as " return await ctx.send(
"they're a member of staff.") "I can't ban this user as " "they're a member of staff."
)
userlog(target.id, ctx.author, reason, "bans", target.name) userlog(target.id, ctx.author, reason, "bans", target.name)
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
await target.ban(reason=f"{ctx.author}, reason: {reason}", await target.ban(
delete_message_days=0) reason=f"{ctx.author}, reason: {reason}", delete_message_days=0
chan_message = f"⛔ **Silent ban**: {ctx.author.mention} banned "\ )
f"{target.mention} | {safe_name}\n"\ chan_message = (
f"🏷 __User ID__: {target.id}\n" f"⛔ **Silent ban**: {ctx.author.mention} banned "
f"{target.mention} | {safe_name}\n"
f"🏷 __User ID__: {target.id}\n"
)
if reason: if reason:
chan_message += f"✏️ __Reason__: \"{reason}\"" chan_message += f'✏️ __Reason__: "{reason}"'
else: else:
chan_message += "Please add an explanation below. In the future"\ chan_message += (
", it is recommended to use `.ban <user> [reason]`"\ "Please add an explanation below. In the future"
" as the reason is automatically sent to the user." ", it is recommended to use `.ban <user> [reason]`"
" as the reason is automatically sent to the user."
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
@ -276,12 +312,12 @@ class Mod(Cog):
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command() @commands.command()
async def approve(self, ctx, target: discord.Member, async def approve(self, ctx, target: discord.Member, role: str = "community"):
role: str = "community"):
"""Add a role to a user (default: community), staff only.""" """Add a role to a user (default: community), staff only."""
if role not in config.named_roles: if role not in config.named_roles:
return await ctx.send("No such role! Available roles: " + return await ctx.send(
','.join(config.named_roles)) "No such role! Available roles: " + ",".join(config.named_roles)
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
target_role = ctx.guild.get_role(config.named_roles[role]) target_role = ctx.guild.get_role(config.named_roles[role])
@ -293,18 +329,19 @@ class Mod(Cog):
await ctx.send(f"Approved {target.mention} to `{role}` role.") await ctx.send(f"Approved {target.mention} to `{role}` role.")
await log_channel.send(f"✅ Approved: {ctx.author.mention} added" await log_channel.send(
f" {role} to {target.mention}") f"✅ Approved: {ctx.author.mention} added" f" {role} to {target.mention}"
)
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command(aliases=["unapprove"]) @commands.command(aliases=["unapprove"])
async def revoke(self, ctx, target: discord.Member, async def revoke(self, ctx, target: discord.Member, role: str = "community"):
role: str = "community"):
"""Remove a role from a user (default: community), staff only.""" """Remove a role from a user (default: community), staff only."""
if role not in config.named_roles: if role not in config.named_roles:
return await ctx.send("No such role! Available roles: " + return await ctx.send(
','.join(config.named_roles)) "No such role! Available roles: " + ",".join(config.named_roles)
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
target_role = ctx.guild.get_role(config.named_roles[role]) target_role = ctx.guild.get_role(config.named_roles[role])
@ -316,8 +353,10 @@ class Mod(Cog):
await ctx.send(f"Un-approved {target.mention} from `{role}` role.") await ctx.send(f"Un-approved {target.mention} from `{role}` role.")
await log_channel.send(f"❌ Un-approved: {ctx.author.mention} removed" await log_channel.send(
f" {role} from {target.mention}") f"❌ Un-approved: {ctx.author.mention} removed"
f" {role} from {target.mention}"
)
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@ -328,8 +367,10 @@ class Mod(Cog):
if not channel: if not channel:
channel = ctx.channel channel = ctx.channel
await channel.purge(limit=limit) await channel.purge(limit=limit)
msg = f"🗑 **Purged**: {ctx.author.mention} purged {limit} "\ msg = (
f"messages in {channel.mention}." f"🗑 **Purged**: {ctx.author.mention} purged {limit} "
f"messages in {channel.mention}."
)
await log_channel.send(msg) await log_channel.send(msg)
@commands.guild_only() @commands.guild_only()
@ -341,37 +382,46 @@ class Mod(Cog):
if target == ctx.author: if target == ctx.author:
return await ctx.send("You can't do mod actions on yourself.") return await ctx.send("You can't do mod actions on yourself.")
elif target == self.bot.user: elif target == self.bot.user:
return await ctx.send(f"I'm sorry {ctx.author.mention}, " return await ctx.send(
"I'm afraid I can't do that.") f"I'm sorry {ctx.author.mention}, " "I'm afraid I can't do that."
)
elif self.check_if_target_is_staff(target): elif self.check_if_target_is_staff(target):
return await ctx.send("I can't warn this user as " return await ctx.send(
"they're a member of staff.") "I can't warn this user as " "they're a member of staff."
)
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
warn_count = userlog(target.id, ctx.author, reason, warn_count = userlog(target.id, ctx.author, reason, "warns", target.name)
"warns", target.name)
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
chan_msg = f"⚠️ **Warned**: {ctx.author.mention} warned "\ chan_msg = (
f"{target.mention} (warn #{warn_count}) "\ f"⚠️ **Warned**: {ctx.author.mention} warned "
f"| {safe_name}\n" f"{target.mention} (warn #{warn_count}) "
f"| {safe_name}\n"
)
msg = f"You were warned on {ctx.guild.name}." msg = f"You were warned on {ctx.guild.name}."
if reason: if reason:
msg += " The given reason is: " + reason msg += " The given reason is: " + reason
msg += f"\n\nPlease read the rules in {config.rules_url}. "\ msg += (
f"This is warn #{warn_count}." f"\n\nPlease read the rules in {config.rules_url}. "
f"This is warn #{warn_count}."
)
if warn_count == 2: if warn_count == 2:
msg += " __The next warn will automatically kick.__" msg += " __The next warn will automatically kick.__"
if warn_count == 3: if warn_count == 3:
msg += "\n\nYou were kicked because of this warning. "\ msg += (
"You can join again right away. "\ "\n\nYou were kicked because of this warning. "
"Two more warnings will result in an automatic ban." "You can join again right away. "
"Two more warnings will result in an automatic ban."
)
if warn_count == 4: if warn_count == 4:
msg += "\n\nYou were kicked because of this warning. "\ msg += (
"This is your final warning. "\ "\n\nYou were kicked because of this warning. "
"You can join again, but "\ "This is your final warning. "
"**one more warn will result in a ban**." "You can join again, but "
"**one more warn will result in a ban**."
)
chan_msg += "**This resulted in an auto-kick.**\n" chan_msg += "**This resulted in an auto-kick.**\n"
if warn_count == 5: if warn_count == 5:
msg += "\n\nYou were automatically banned due to five warnings." msg += "\n\nYou were automatically banned due to five warnings."
@ -385,17 +435,19 @@ class Mod(Cog):
if warn_count == 3 or warn_count == 4: if warn_count == 3 or warn_count == 4:
await target.kick() await target.kick()
if warn_count >= 5: # just in case if warn_count >= 5: # just in case
await target.ban(reason="exceeded warn limit", await target.ban(reason="exceeded warn limit", delete_message_days=0)
delete_message_days=0) await ctx.send(
await ctx.send(f"{target.mention} warned. " f"{target.mention} warned. " f"User has {warn_count} warning(s)."
f"User has {warn_count} warning(s).") )
if reason: if reason:
chan_msg += f"✏️ __Reason__: \"{reason}\"" chan_msg += f'✏️ __Reason__: "{reason}"'
else: else:
chan_msg += "Please add an explanation below. In the future"\ chan_msg += (
", it is recommended to use `.warn <user> [reason]`"\ "Please add an explanation below. In the future"
" as the reason is automatically sent to the user." ", it is recommended to use `.warn <user> [reason]`"
" as the reason is automatically sent to the user."
)
await log_channel.send(chan_msg) await log_channel.send(chan_msg)
@commands.guild_only() @commands.guild_only()
@ -415,7 +467,7 @@ class Mod(Cog):
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command(aliases=['echo']) @commands.command(aliases=["echo"])
async def say(self, ctx, *, the_text: str): async def say(self, ctx, *, the_text: str):
"""Repeats a given text, staff only.""" """Repeats a given text, staff only."""
await ctx.send(the_text) await ctx.send(the_text)

View file

@ -14,8 +14,7 @@ class ModNote(Cog):
@commands.command(aliases=["addnote"]) @commands.command(aliases=["addnote"])
async def note(self, ctx, target: discord.Member, *, note: str = ""): async def note(self, ctx, target: discord.Member, *, note: str = ""):
"""Adds a note to a user, staff only.""" """Adds a note to a user, staff only."""
userlog(target.id, ctx.author, note, userlog(target.id, ctx.author, note, "notes", target.name)
"notes", target.name)
await ctx.send(f"{ctx.author.mention}: noted!") await ctx.send(f"{ctx.author.mention}: noted!")
@commands.guild_only() @commands.guild_only()
@ -23,8 +22,7 @@ class ModNote(Cog):
@commands.command(aliases=["addnoteid"]) @commands.command(aliases=["addnoteid"])
async def noteid(self, ctx, target: int, *, note: str = ""): async def noteid(self, ctx, target: int, *, note: str = ""):
"""Adds a note to a user by userid, staff only.""" """Adds a note to a user by userid, staff only."""
userlog(target, ctx.author, note, userlog(target, ctx.author, note, "notes")
"notes")
await ctx.send(f"{ctx.author.mention}: noted!") await ctx.send(f"{ctx.author.mention}: noted!")

View file

@ -13,9 +13,14 @@ class ModReact(Cog):
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command() @commands.command()
async def clearreactsbyuser(self, ctx, user: discord.Member, *, async def clearreactsbyuser(
channel: discord.TextChannel = None, self,
limit: int = 50): ctx,
user: discord.Member,
*,
channel: discord.TextChannel = None,
limit: int = 50,
):
"""Clears reacts from a given user in the given channel, staff only.""" """Clears reacts from a given user in the given channel, staff only."""
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
if not channel: if not channel:
@ -27,18 +32,20 @@ class ModReact(Cog):
count += 1 count += 1
async for u in react.users(): async for u in react.users():
await msg.remove_reaction(react, u) await msg.remove_reaction(react, u)
msg = f"✏️ **Cleared reacts**: {ctx.author.mention} cleared "\ msg = (
f"{user.mention}'s reacts from the last {limit} messages "\ f"✏️ **Cleared reacts**: {ctx.author.mention} cleared "
f"in {channel.mention}." f"{user.mention}'s reacts from the last {limit} messages "
f"in {channel.mention}."
)
await ctx.channel.send(f"Cleared {count} unique reactions") await ctx.channel.send(f"Cleared {count} unique reactions")
await log_channel.send(msg) await log_channel.send(msg)
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command() @commands.command()
async def clearallreacts(self, ctx, *, async def clearallreacts(
limit: int = 50, self, ctx, *, limit: int = 50, channel: discord.TextChannel = None
channel: discord.TextChannel = None): ):
"""Clears all reacts in a given channel, staff only. Use with care.""" """Clears all reacts in a given channel, staff only. Use with care."""
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
if not channel: if not channel:
@ -48,8 +55,10 @@ class ModReact(Cog):
if msg.reactions: if msg.reactions:
count += 1 count += 1
await msg.clear_reactions() await msg.clear_reactions()
msg = f"✏️ **Cleared reacts**: {ctx.author.mention} cleared all "\ msg = (
f"reacts from the last {limit} messages in {channel.mention}." f"✏️ **Cleared reacts**: {ctx.author.mention} cleared all "
f"reacts from the last {limit} messages in {channel.mention}."
)
await ctx.channel.send(f"Cleared reacts from {count} messages!") await ctx.channel.send(f"Cleared reacts from {count} messages!")
await log_channel.send(msg) await log_channel.send(msg)
@ -58,8 +67,10 @@ class ModReact(Cog):
@commands.command() @commands.command()
async def clearreactsinteractive(self, ctx): async def clearreactsinteractive(self, ctx):
"""Clears reacts interactively, staff only. Use with care.""" """Clears reacts interactively, staff only. Use with care."""
msg_text = f"{ctx.author.mention}, react to the reactions you want "\ msg_text = (
f"to remove. React to this message when you're done." f"{ctx.author.mention}, react to the reactions you want "
f"to remove. React to this message when you're done."
)
msg = await ctx.channel.send(msg_text) msg = await ctx.channel.send(msg_text)
tasks = [] tasks = []
@ -74,10 +85,11 @@ class ModReact(Cog):
else: else:
# remove a reaction # remove a reaction
async def impl(): async def impl():
msg = await self.bot \ msg = (
.get_guild(event.guild_id) \ await self.bot.get_guild(event.guild_id)
.get_channel(event.channel_id) \ .get_channel(event.channel_id)
.get_message(event.message_id) .get_message(event.message_id)
)
def check_emoji(r): def check_emoji(r):
if event.emoji.is_custom_emoji() == r.custom_emoji: if event.emoji.is_custom_emoji() == r.custom_emoji:
@ -88,17 +100,17 @@ class ModReact(Cog):
return event.emoji.name == r.emoji return event.emoji.name == r.emoji
else: else:
return False return False
for reaction in filter(check_emoji, msg.reactions): for reaction in filter(check_emoji, msg.reactions):
async for u in reaction.users(): async for u in reaction.users():
await reaction.message.remove_reaction(reaction, u) await reaction.message.remove_reaction(reaction, u)
# schedule immediately # schedule immediately
tasks.append(asyncio.create_task(impl())) tasks.append(asyncio.create_task(impl()))
return False return False
try: try:
await self.bot.wait_for("raw_reaction_add", await self.bot.wait_for("raw_reaction_add", timeout=120.0, check=check)
timeout=120.0,
check=check)
except asyncio.TimeoutError: except asyncio.TimeoutError:
await msg.edit(content=f"{msg_text} Timed out.") await msg.edit(content=f"{msg_text} Timed out.")
else: else:

View file

@ -20,31 +20,37 @@ class ModTimed(Cog):
@commands.bot_has_permissions(ban_members=True) @commands.bot_has_permissions(ban_members=True)
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command() @commands.command()
async def timeban(self, ctx, target: discord.Member, async def timeban(
duration: str, *, reason: str = ""): self, ctx, target: discord.Member, duration: str, *, reason: str = ""
):
"""Bans a user for a specified amount of time, staff only.""" """Bans a user for a specified amount of time, staff only."""
# Hedge-proofing the code # Hedge-proofing the code
if target == ctx.author: if target == ctx.author:
return await ctx.send("You can't do mod actions on yourself.") return await ctx.send("You can't do mod actions on yourself.")
elif self.check_if_target_is_staff(target): elif self.check_if_target_is_staff(target):
return await ctx.send("I can't ban this user as " return await ctx.send(
"they're a member of staff.") "I can't ban this user as " "they're a member of staff."
)
expiry_timestamp = self.bot.parse_time(duration) expiry_timestamp = self.bot.parse_time(duration)
expiry_datetime = datetime.utcfromtimestamp(expiry_timestamp) expiry_datetime = datetime.utcfromtimestamp(expiry_timestamp)
duration_text = self.bot.get_relative_timestamp(time_to=expiry_datetime, duration_text = self.bot.get_relative_timestamp(
include_to=True, time_to=expiry_datetime, include_to=True, humanized=True
humanized=True) )
userlog(target.id, ctx.author, f"{reason} (Timed, until " userlog(
f"{duration_text})", target.id,
"bans", target.name) ctx.author,
f"{reason} (Timed, until " f"{duration_text})",
"bans",
target.name,
)
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
dm_message = f"You were banned from {ctx.guild.name}." dm_message = f"You were banned from {ctx.guild.name}."
if reason: if reason:
dm_message += f" The given reason is: \"{reason}\"." dm_message += f' The given reason is: "{reason}".'
dm_message += f"\n\nThis ban will expire {duration_text}." dm_message += f"\n\nThis ban will expire {duration_text}."
try: try:
@ -54,53 +60,63 @@ class ModTimed(Cog):
# or has DMs disabled # or has DMs disabled
pass pass
await target.ban(reason=f"{ctx.author}, reason: {reason}", await target.ban(
delete_message_days=0) reason=f"{ctx.author}, reason: {reason}", delete_message_days=0
chan_message = f"⛔ **Timed Ban**: {ctx.author.mention} banned "\ )
f"{target.mention} for {duration_text} | {safe_name}\n"\ chan_message = (
f"🏷 __User ID__: {target.id}\n" f"⛔ **Timed Ban**: {ctx.author.mention} banned "
f"{target.mention} for {duration_text} | {safe_name}\n"
f"🏷 __User ID__: {target.id}\n"
)
if reason: if reason:
chan_message += f"✏️ __Reason__: \"{reason}\"" chan_message += f'✏️ __Reason__: "{reason}"'
else: else:
chan_message += "Please add an explanation below. In the future"\ chan_message += (
", it is recommended to use `.ban <user> [reason]`"\ "Please add an explanation below. In the future"
" as the reason is automatically sent to the user." ", it is recommended to use `.ban <user> [reason]`"
" as the reason is automatically sent to the user."
)
add_job("unban", target.id, {"guild": ctx.guild.id}, expiry_timestamp) add_job("unban", target.id, {"guild": ctx.guild.id}, expiry_timestamp)
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
await ctx.send(f"{safe_name} is now b&. " await ctx.send(f"{safe_name} is now b&. " f"It will expire {duration_text}. 👍")
f"It will expire {duration_text}. 👍")
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command() @commands.command()
async def timemute(self, ctx, target: discord.Member, async def timemute(
duration: str, *, reason: str = ""): self, ctx, target: discord.Member, duration: str, *, reason: str = ""
):
"""Mutes a user for a specified amount of time, staff only.""" """Mutes a user for a specified amount of time, staff only."""
# Hedge-proofing the code # Hedge-proofing the code
if target == ctx.author: if target == ctx.author:
return await ctx.send("You can't do mod actions on yourself.") return await ctx.send("You can't do mod actions on yourself.")
elif self.check_if_target_is_staff(target): elif self.check_if_target_is_staff(target):
return await ctx.send("I can't mute this user as " return await ctx.send(
"they're a member of staff.") "I can't mute this user as " "they're a member of staff."
)
expiry_timestamp = self.bot.parse_time(duration) expiry_timestamp = self.bot.parse_time(duration)
expiry_datetime = datetime.utcfromtimestamp(expiry_timestamp) expiry_datetime = datetime.utcfromtimestamp(expiry_timestamp)
duration_text = self.bot.get_relative_timestamp(time_to=expiry_datetime, duration_text = self.bot.get_relative_timestamp(
include_to=True, time_to=expiry_datetime, include_to=True, humanized=True
humanized=True) )
userlog(target.id, ctx.author, f"{reason} (Timed, until " userlog(
f"{duration_text})", target.id,
"mutes", target.name) ctx.author,
f"{reason} (Timed, until " f"{duration_text})",
"mutes",
target.name,
)
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
dm_message = f"You were muted!" dm_message = f"You were muted!"
if reason: if reason:
dm_message += f" The given reason is: \"{reason}\"." dm_message += f' The given reason is: "{reason}".'
dm_message += f"\n\nThis mute will expire {duration_text}." dm_message += f"\n\nThis mute will expire {duration_text}."
try: try:
@ -114,22 +130,27 @@ class ModTimed(Cog):
await target.add_roles(mute_role, reason=str(ctx.author)) await target.add_roles(mute_role, reason=str(ctx.author))
chan_message = f"🔇 **Timed Mute**: {ctx.author.mention} muted "\ chan_message = (
f"{target.mention} for {duration_text} | {safe_name}\n"\ f"🔇 **Timed Mute**: {ctx.author.mention} muted "
f"🏷 __User ID__: {target.id}\n" f"{target.mention} for {duration_text} | {safe_name}\n"
f"🏷 __User ID__: {target.id}\n"
)
if reason: if reason:
chan_message += f"✏️ __Reason__: \"{reason}\"" chan_message += f'✏️ __Reason__: "{reason}"'
else: else:
chan_message += "Please add an explanation below. In the future, "\ chan_message += (
"it is recommended to use `.mute <user> [reason]`"\ "Please add an explanation below. In the future, "
" as the reason is automatically sent to the user." "it is recommended to use `.mute <user> [reason]`"
" as the reason is automatically sent to the user."
)
add_job("unmute", target.id, {"guild": ctx.guild.id}, expiry_timestamp) add_job("unmute", target.id, {"guild": ctx.guild.id}, expiry_timestamp)
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(chan_message) await log_channel.send(chan_message)
await ctx.send(f"{target.mention} can no longer speak. " await ctx.send(
f"It will expire {duration_text}.") f"{target.mention} can no longer speak. " f"It will expire {duration_text}."
)
add_restriction(target.id, config.mute_role) add_restriction(target.id, config.mute_role)

View file

@ -11,8 +11,9 @@ class ModUserlog(Cog):
def __init__(self, bot): def __init__(self, bot):
self.bot = bot self.bot = bot
def get_userlog_embed_for_id(self, uid: str, name: str, own: bool = False, def get_userlog_embed_for_id(
event=""): self, uid: str, name: str, own: bool = False, event=""
):
own_note = " Good for you!" if own else "" own_note = " Good for you!" if own else ""
wanted_events = ["warns", "bans", "kicks", "mutes"] wanted_events = ["warns", "bans", "kicks", "mutes"]
if event and not isinstance(event, list): if event and not isinstance(event, list):
@ -30,12 +31,17 @@ class ModUserlog(Cog):
if event_type in userlog[uid] and userlog[uid][event_type]: if event_type in userlog[uid] and userlog[uid][event_type]:
event_name = userlog_event_types[event_type] event_name = userlog_event_types[event_type]
for idx, event in enumerate(userlog[uid][event_type]): for idx, event in enumerate(userlog[uid][event_type]):
issuer = "" if own else f"Issuer: {event['issuer_name']} "\ issuer = (
f"({event['issuer_id']})\n" ""
embed.add_field(name=f"{event_name} {idx + 1}: " if own
f"{event['timestamp']}", else f"Issuer: {event['issuer_name']} "
value=issuer + f"Reason: {event['reason']}", f"({event['issuer_id']})\n"
inline=False) )
embed.add_field(
name=f"{event_name} {idx + 1}: " f"{event['timestamp']}",
value=issuer + f"Reason: {event['reason']}",
inline=False,
)
if not own and "watch" in userlog[uid]: if not own and "watch" in userlog[uid]:
watch_state = "" if userlog[uid]["watch"] else "NOT " watch_state = "" if userlog[uid]["watch"] else "NOT "
@ -65,17 +71,17 @@ class ModUserlog(Cog):
if not event_count: if not event_count:
return f"<@{uid}> has no {event_type}!" return f"<@{uid}> has no {event_type}!"
if idx > event_count: if idx > event_count:
return "Index is higher than "\ return "Index is higher than " f"count ({event_count})!"
f"count ({event_count})!"
if idx < 1: if idx < 1:
return "Index is below 1!" return "Index is below 1!"
event = userlog[uid][event_type][idx - 1] event = userlog[uid][event_type][idx - 1]
event_name = userlog_event_types[event_type] event_name = userlog_event_types[event_type]
embed = discord.Embed(color=discord.Color.dark_red(), embed = discord.Embed(
title=f"{event_name} {idx} on " color=discord.Color.dark_red(),
f"{event['timestamp']}", title=f"{event_name} {idx} on " f"{event['timestamp']}",
description=f"Issuer: {event['issuer_name']}\n" description=f"Issuer: {event['issuer_name']}\n"
f"Reason: {event['reason']}") f"Reason: {event['reason']}",
)
del userlog[uid][event_type][idx - 1] del userlog[uid][event_type][idx - 1]
set_userlog(json.dumps(userlog)) set_userlog(json.dumps(userlog))
return embed return embed
@ -85,21 +91,18 @@ class ModUserlog(Cog):
@commands.command(aliases=["events"]) @commands.command(aliases=["events"])
async def eventtypes(self, ctx): async def eventtypes(self, ctx):
"""Lists the available event types, staff only.""" """Lists the available event types, staff only."""
event_list = [f"{et} ({userlog_event_types[et]})" for et in event_list = [f"{et} ({userlog_event_types[et]})" for et in userlog_event_types]
userlog_event_types] event_text = "Available events:\n``` - " + "\n - ".join(event_list) + "```"
event_text = ("Available events:\n``` - " +
"\n - ".join(event_list) +
"```")
await ctx.send(event_text) await ctx.send(event_text)
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command(name="userlog", @commands.command(
aliases=["listwarns", "getuserlog", "listuserlog"]) name="userlog", aliases=["listwarns", "getuserlog", "listuserlog"]
)
async def userlog_cmd(self, ctx, target: discord.Member, event=""): async def userlog_cmd(self, ctx, target: discord.Member, event=""):
"""Lists the userlog events for a user, staff only.""" """Lists the userlog events for a user, staff only."""
embed = self.get_userlog_embed_for_id(str(target.id), str(target), embed = self.get_userlog_embed_for_id(str(target.id), str(target), event=event)
event=event)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.guild_only() @commands.guild_only()
@ -107,16 +110,16 @@ class ModUserlog(Cog):
@commands.command(aliases=["listnotes", "usernotes"]) @commands.command(aliases=["listnotes", "usernotes"])
async def notes(self, ctx, target: discord.Member): async def notes(self, ctx, target: discord.Member):
"""Lists the notes for a user, staff only.""" """Lists the notes for a user, staff only."""
embed = self.get_userlog_embed_for_id(str(target.id), str(target), embed = self.get_userlog_embed_for_id(
event="notes") str(target.id), str(target), event="notes"
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.guild_only() @commands.guild_only()
@commands.command(aliases=["mywarns"]) @commands.command(aliases=["mywarns"])
async def myuserlog(self, ctx): async def myuserlog(self, ctx):
"""Lists your userlog events (warns etc).""" """Lists your userlog events (warns etc)."""
embed = self.get_userlog_embed_for_id(str(ctx.author.id), embed = self.get_userlog_embed_for_id(str(ctx.author.id), str(ctx.author), True)
str(ctx.author), True)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.guild_only() @commands.guild_only()
@ -130,16 +133,17 @@ class ModUserlog(Cog):
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command(aliases=["clearwarns"]) @commands.command(aliases=["clearwarns"])
async def clearevent(self, ctx, target: discord.Member, async def clearevent(self, ctx, target: discord.Member, event="warns"):
event="warns"):
"""Clears all events of given type for a user, staff only.""" """Clears all events of given type for a user, staff only."""
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
msg = self.clear_event_from_id(str(target.id), event) msg = self.clear_event_from_id(str(target.id), event)
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
await ctx.send(msg) await ctx.send(msg)
msg = f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"\ msg = (
f" all {event} events of {target.mention} | "\ f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"
f"{safe_name}" f" all {event} events of {target.mention} | "
f"{safe_name}"
)
await log_channel.send(msg) await log_channel.send(msg)
@commands.guild_only() @commands.guild_only()
@ -150,15 +154,16 @@ class ModUserlog(Cog):
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
msg = self.clear_event_from_id(str(target), event) msg = self.clear_event_from_id(str(target), event)
await ctx.send(msg) await ctx.send(msg)
msg = f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"\ msg = (
f" all {event} events of <@{target}> " f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"
f" all {event} events of <@{target}> "
)
await log_channel.send(msg) await log_channel.send(msg)
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command(aliases=["delwarn"]) @commands.command(aliases=["delwarn"])
async def delevent(self, ctx, target: discord.Member, idx: int, async def delevent(self, ctx, target: discord.Member, idx: int, event="warns"):
event="warns"):
"""Removes a specific event from a user, staff only.""" """Removes a specific event from a user, staff only."""
log_channel = self.bot.get_channel(config.modlog_channel) log_channel = self.bot.get_channel(config.modlog_channel)
del_event = self.delete_event_from_id(str(target.id), idx, event) del_event = self.delete_event_from_id(str(target.id), idx, event)
@ -167,9 +172,11 @@ class ModUserlog(Cog):
if isinstance(del_event, discord.Embed): if isinstance(del_event, discord.Embed):
await ctx.send(f"{target.mention} has a {event_name} removed!") await ctx.send(f"{target.mention} has a {event_name} removed!")
safe_name = await commands.clean_content().convert(ctx, str(target)) safe_name = await commands.clean_content().convert(ctx, str(target))
msg = f"🗑 **Deleted {event_name}**: "\ msg = (
f"{ctx.author.mention} removed "\ f"🗑 **Deleted {event_name}**: "
f"{event_name} {idx} from {target.mention} | {safe_name}" f"{ctx.author.mention} removed "
f"{event_name} {idx} from {target.mention} | {safe_name}"
)
await log_channel.send(msg, embed=del_event) await log_channel.send(msg, embed=del_event)
else: else:
await ctx.send(del_event) await ctx.send(del_event)
@ -185,9 +192,11 @@ class ModUserlog(Cog):
# This is hell. # This is hell.
if isinstance(del_event, discord.Embed): if isinstance(del_event, discord.Embed):
await ctx.send(f"<@{target}> has a {event_name} removed!") await ctx.send(f"<@{target}> has a {event_name} removed!")
msg = f"🗑 **Deleted {event_name}**: "\ msg = (
f"{ctx.author.mention} removed "\ f"🗑 **Deleted {event_name}**: "
f"{event_name} {idx} from <@{target}> " f"{ctx.author.mention} removed "
f"{event_name} {idx} from <@{target}> "
)
await log_channel.send(msg, embed=del_event) await log_channel.send(msg, embed=del_event)
else: else:
await ctx.send(del_event) await ctx.send(del_event)
@ -202,23 +211,26 @@ class ModUserlog(Cog):
role = "@ everyone" role = "@ everyone"
event_types = ["warns", "bans", "kicks", "mutes", "notes"] event_types = ["warns", "bans", "kicks", "mutes", "notes"]
embed = self.get_userlog_embed_for_id(str(user.id), str(user), embed = self.get_userlog_embed_for_id(
event=event_types) str(user.id), str(user), event=event_types
)
user_name = await commands.clean_content().convert(ctx, user.name) user_name = await commands.clean_content().convert(ctx, user.name)
display_name = await commands.clean_content().convert(ctx, user.display_name) display_name = await commands.clean_content().convert(ctx, user.display_name)
await ctx.send(f"user = {user_name}\n" await ctx.send(
f"id = {user.id}\n" f"user = {user_name}\n"
f"avatar = {user.avatar_url}\n" f"id = {user.id}\n"
f"bot = {user.bot}\n" f"avatar = {user.avatar_url}\n"
f"created_at = {user.created_at}\n" f"bot = {user.bot}\n"
f"display_name = {display_name}\n" f"created_at = {user.created_at}\n"
f"joined_at = {user.joined_at}\n" f"display_name = {display_name}\n"
f"activities = `{user.activities}`\n" f"joined_at = {user.joined_at}\n"
f"color = {user.colour}\n" f"activities = `{user.activities}`\n"
f"top_role = {role}\n", f"color = {user.colour}\n"
embed=embed) f"top_role = {role}\n",
embed=embed,
)
def setup(bot): def setup(bot):

View file

@ -8,6 +8,7 @@ import gidgethub.aiohttp
from helpers.checks import check_if_collaborator from helpers.checks import check_if_collaborator
from helpers.checks import check_if_pin_channel from helpers.checks import check_if_pin_channel
class Pin(Cog): class Pin(Cog):
""" """
Allow users to pin things Allow users to pin things
@ -17,9 +18,11 @@ class Pin(Cog):
self.bot = bot self.bot = bot
def is_pinboard(self, msg): def is_pinboard(self, msg):
return msg.author == self.bot.user and \ return (
len(msg.embeds) > 0 and \ msg.author == self.bot.user
msg.embeds[0].title == "Pinboard" and len(msg.embeds) > 0
and msg.embeds[0].title == "Pinboard"
)
async def get_pinboard(self, gh, channel): async def get_pinboard(self, gh, channel):
# Find pinboard pin # Find pinboard pin
@ -32,21 +35,25 @@ class Pin(Cog):
return (id, data["files"]["pinboard.md"]["content"]) return (id, data["files"]["pinboard.md"]["content"])
# Create pinboard pin if it does not exist # Create pinboard pin if it does not exist
data = await gh.post("/gists", data={ data = await gh.post(
"files": { "/gists",
"pinboard.md": { data={
"content": "Old pins are available here:\n\n" "files": {
} "pinboard.md": {"content": "Old pins are available here:\n\n"}
},
"description": f"Pinboard for SwitchRoot #{channel.name}",
"public": True,
}, },
"description": f"Pinboard for SwitchRoot #{channel.name}", )
"public": True
})
msg = await channel.send(embed=Embed( msg = await channel.send(
title="Pinboard", embed=Embed(
description="Old pins are moved to the pinboard to make space for \ title="Pinboard",
description="Old pins are moved to the pinboard to make space for \
new ones. Check it out!", new ones. Check it out!",
url=data["html_url"])) url=data["html_url"],
)
)
await msg.pin() await msg.pin()
return (data["id"], data["files"]["pinboard.md"]["content"]) return (data["id"], data["files"]["pinboard.md"]["content"])
@ -57,18 +64,15 @@ class Pin(Cog):
return return
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
gh = gidgethub.aiohttp.GitHubAPI(session, "RoboCop-NG", gh = gidgethub.aiohttp.GitHubAPI(
oauth_token=config.github_oauth_token) session, "RoboCop-NG", oauth_token=config.github_oauth_token
)
(id, content) = await self.get_pinboard(gh, channel) (id, content) = await self.get_pinboard(gh, channel)
content += "- " + data + "\n" content += "- " + data + "\n"
await gh.patch(f"/gists/{id}", data={ await gh.patch(
"files": { f"/gists/{id}", data={"files": {"pinboard.md": {"content": content}}}
"pinboard.md": { )
"content": content
}
}
})
@commands.command() @commands.command()
@commands.guild_only() @commands.guild_only()
@ -136,7 +140,7 @@ class Pin(Cog):
break break
# Wait for the automated "Pinned" message so we can delete it # Wait for the automated "Pinned" message so we can delete it
waitable = self.bot.wait_for('message', check=check) waitable = self.bot.wait_for("message", check=check)
# Pin the message # Pin the message
await target_msg.pin() await target_msg.pin()

View file

@ -22,12 +22,15 @@ class Remind(Cog):
if uid not in ctab["remind"][jobtimestamp]: if uid not in ctab["remind"][jobtimestamp]:
continue continue
job_details = ctab["remind"][jobtimestamp][uid] job_details = ctab["remind"][jobtimestamp][uid]
expiry_timestr = datetime.utcfromtimestamp(int(jobtimestamp))\ expiry_timestr = datetime.utcfromtimestamp(int(jobtimestamp)).strftime(
.strftime('%Y-%m-%d %H:%M:%S (UTC)') "%Y-%m-%d %H:%M:%S (UTC)"
embed.add_field(name=f"Reminder for {expiry_timestr}", )
value=f"Added on: {job_details['added']}, " embed.add_field(
f"Text: {job_details['text']}", name=f"Reminder for {expiry_timestr}",
inline=False) value=f"Added on: {job_details['added']}, "
f"Text: {job_details['text']}",
inline=False,
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.cooldown(1, 60, type=commands.BucketType.user) @commands.cooldown(1, 60, type=commands.BucketType.user)
@ -40,27 +43,32 @@ class Remind(Cog):
expiry_timestamp = self.bot.parse_time(when) expiry_timestamp = self.bot.parse_time(when)
if current_timestamp + 5 > expiry_timestamp: if current_timestamp + 5 > expiry_timestamp:
msg = await ctx.send(f"{ctx.author.mention}: Minimum " msg = await ctx.send(
"remind interval is 5 seconds.") f"{ctx.author.mention}: Minimum " "remind interval is 5 seconds."
)
await asyncio.sleep(5) await asyncio.sleep(5)
await msg.delete() await msg.delete()
return return
expiry_datetime = datetime.utcfromtimestamp(expiry_timestamp) expiry_datetime = datetime.utcfromtimestamp(expiry_timestamp)
duration_text = self.bot.get_relative_timestamp(time_to=expiry_datetime, duration_text = self.bot.get_relative_timestamp(
include_to=True, time_to=expiry_datetime, include_to=True, humanized=True
humanized=True) )
safe_text = await commands.clean_content().convert(ctx, str(text)) safe_text = await commands.clean_content().convert(ctx, str(text))
added_on = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S (UTC)") added_on = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S (UTC)")
add_job("remind", add_job(
ctx.author.id, "remind",
{"text": safe_text, "added": added_on}, ctx.author.id,
expiry_timestamp) {"text": safe_text, "added": added_on},
expiry_timestamp,
)
msg = await ctx.send(f"{ctx.author.mention}: I'll remind you in " msg = await ctx.send(
f"DMs about `{safe_text}` in {duration_text}.") f"{ctx.author.mention}: I'll remind you in "
f"DMs about `{safe_text}` in {duration_text}."
)
await asyncio.sleep(5) await asyncio.sleep(5)
await msg.delete() await msg.delete()

View file

@ -33,17 +33,17 @@ class Robocronp(Cog):
for jobtimestamp in ctab[jobtype]: for jobtimestamp in ctab[jobtype]:
for job_name in ctab[jobtype][jobtimestamp]: for job_name in ctab[jobtype][jobtimestamp]:
job_details = repr(ctab[jobtype][jobtimestamp][job_name]) job_details = repr(ctab[jobtype][jobtimestamp][job_name])
embed.add_field(name=f"{jobtype} for {job_name}", embed.add_field(
value=f"Timestamp: {jobtimestamp}, " name=f"{jobtype} for {job_name}",
f"Details: {job_details}", value=f"Timestamp: {jobtimestamp}, " f"Details: {job_details}",
inline=False) inline=False,
)
await ctx.send(embed=embed) await ctx.send(embed=embed)
@commands.guild_only() @commands.guild_only()
@commands.check(check_if_staff) @commands.check(check_if_staff)
@commands.command(aliases=["removejob"]) @commands.command(aliases=["removejob"])
async def deletejob(self, ctx, timestamp: str, async def deletejob(self, ctx, timestamp: str, job_type: str, job_name: str):
job_type: str, job_name: str):
"""Removes a timed robocronp job, staff only. """Removes a timed robocronp job, staff only.
You'll need to supply: You'll need to supply:
@ -64,31 +64,34 @@ class Robocronp(Cog):
target_user = await self.bot.fetch_user(job_name) target_user = await self.bot.fetch_user(job_name)
target_guild = self.bot.get_guild(job_details["guild"]) target_guild = self.bot.get_guild(job_details["guild"])
delete_job(timestamp, jobtype, job_name) delete_job(timestamp, jobtype, job_name)
await target_guild.unban(target_user, await target_guild.unban(
reason="Robocronp: Timed " target_user, reason="Robocronp: Timed " "ban expired."
"ban expired.") )
elif jobtype == "unmute": elif jobtype == "unmute":
remove_restriction(job_name, config.mute_role) remove_restriction(job_name, config.mute_role)
target_guild = self.bot.get_guild(job_details["guild"]) target_guild = self.bot.get_guild(job_details["guild"])
target_member = target_guild.get_member(int(job_name)) target_member = target_guild.get_member(int(job_name))
target_role = target_guild.get_role(config.mute_role) target_role = target_guild.get_role(config.mute_role)
await target_member.remove_roles(target_role, await target_member.remove_roles(
reason="Robocronp: Timed " target_role, reason="Robocronp: Timed " "mute expired."
"mute expired.") )
delete_job(timestamp, jobtype, job_name) delete_job(timestamp, jobtype, job_name)
elif jobtype == "remind": elif jobtype == "remind":
text = job_details["text"] text = job_details["text"]
added_on = job_details["added"] added_on = job_details["added"]
target = await self.bot.fetch_user(int(job_name)) target = await self.bot.fetch_user(int(job_name))
if target: if target:
await target.send("You asked to be reminded about" await target.send(
f" `{text}` on {added_on}.") "You asked to be reminded about" f" `{text}` on {added_on}."
)
delete_job(timestamp, jobtype, job_name) delete_job(timestamp, jobtype, job_name)
except: except:
# Don't kill cronjobs if something goes wrong. # Don't kill cronjobs if something goes wrong.
delete_job(timestamp, jobtype, job_name) delete_job(timestamp, jobtype, job_name)
await log_channel.send("Crondo has errored, job deleted: ```" await log_channel.send(
f"{traceback.format_exc()}```") "Crondo has errored, job deleted: ```"
f"{traceback.format_exc()}```"
)
async def clean_channel(self, channel_id): async def clean_channel(self, channel_id):
log_channel = self.bot.get_channel(config.botlog_channel) log_channel = self.bot.get_channel(config.botlog_channel)
@ -101,12 +104,14 @@ class Robocronp(Cog):
count += len(purge_res) count += len(purge_res)
if len(purge_res) != 100: if len(purge_res) != 100:
done_cleaning = True done_cleaning = True
await log_channel.send(f"Wiped {count} messages from " await log_channel.send(
f"<#{channel.id}> automatically.") f"Wiped {count} messages from " f"<#{channel.id}> automatically."
)
except: except:
# Don't kill cronjobs if something goes wrong. # Don't kill cronjobs if something goes wrong.
await log_channel.send("Cronclean has errored: ```" await log_channel.send(
f"{traceback.format_exc()}```") "Cronclean has errored: ```" f"{traceback.format_exc()}```"
)
async def minutely(self): async def minutely(self):
await self.bot.wait_until_ready() await self.bot.wait_until_ready()
@ -125,8 +130,9 @@ class Robocronp(Cog):
await self.clean_channel(clean_channel) await self.clean_channel(clean_channel)
except: except:
# Don't kill cronjobs if something goes wrong. # Don't kill cronjobs if something goes wrong.
await log_channel.send("Cron-minutely has errored: ```" await log_channel.send(
f"{traceback.format_exc()}```") "Cron-minutely has errored: ```" f"{traceback.format_exc()}```"
)
await asyncio.sleep(60) await asyncio.sleep(60)
async def hourly(self): async def hourly(self):
@ -144,8 +150,9 @@ class Robocronp(Cog):
await self.clean_channel(clean_channel) await self.clean_channel(clean_channel)
except: except:
# Don't kill cronjobs if something goes wrong. # Don't kill cronjobs if something goes wrong.
await log_channel.send("Cron-hourly has errored: ```" await log_channel.send(
f"{traceback.format_exc()}```") "Cron-hourly has errored: ```" f"{traceback.format_exc()}```"
)
# Your stuff that should run an hour after boot # Your stuff that should run an hour after boot
# and after that every hour goes here # and after that every hour goes here
@ -163,8 +170,9 @@ class Robocronp(Cog):
await self.bot.do_resetalgo(verif_channel, "daily robocronp") await self.bot.do_resetalgo(verif_channel, "daily robocronp")
except: except:
# Don't kill cronjobs if something goes wrong. # Don't kill cronjobs if something goes wrong.
await log_channel.send("Cron-daily has errored: ```" await log_channel.send(
f"{traceback.format_exc()}```") "Cron-daily has errored: ```" f"{traceback.format_exc()}```"
)
await asyncio.sleep(86400) await asyncio.sleep(86400)
# Your stuff that should run a day after boot # Your stuff that should run a day after boot
# and after that every day goes here # and after that every day goes here

View file

@ -23,18 +23,20 @@ class Verification(Cog):
await channel.purge(limit=limit) await channel.purge(limit=limit)
await channel.send(config.welcome_header) await channel.send(config.welcome_header)
rules = ['**{}**. {}'.format(i, cleandoc(r)) for i, r in rules = [
enumerate(config.welcome_rules, 1)] "**{}**. {}".format(i, cleandoc(r))
for i, r in enumerate(config.welcome_rules, 1)
]
rule_choice = random.randint(2, len(rules)) rule_choice = random.randint(2, len(rules))
hash_choice_str = self.hash_choice.upper() hash_choice_str = self.hash_choice.upper()
if hash_choice_str == "BLAKE2B": if hash_choice_str == "BLAKE2B":
hash_choice_str += "-512" hash_choice_str += "-512"
elif hash_choice_str == "BLAKE2S": elif hash_choice_str == "BLAKE2S":
hash_choice_str += "-256" hash_choice_str += "-256"
rules[rule_choice - 1] += \ rules[rule_choice - 1] += "\n" + config.hidden_term_line.format(hash_choice_str)
'\n' + config.hidden_term_line.format(hash_choice_str) msg = (
msg = f"🗑 **Reset**: {author} cleared {limit} messages "\ f"🗑 **Reset**: {author} cleared {limit} messages " f" in {channel.mention}"
f" in {channel.mention}" )
msg += f"\n💬 __Current challenge location__: under rule {rule_choice}" msg += f"\n💬 __Current challenge location__: under rule {rule_choice}"
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(msg) await log_channel.send(msg)
@ -64,13 +66,17 @@ class Verification(Cog):
async def do_resetalgo(self, channel, author, limit: int = 100): async def do_resetalgo(self, channel, author, limit: int = 100):
# randomize hash_choice on reset # randomize hash_choice on reset
self.hash_choice = \ self.hash_choice = random.choice(
random.choice(tuple(hashlib.algorithms_guaranteed - tuple(
self.blacklisted_hashes - hashlib.algorithms_guaranteed
{self.hash_choice})) - self.blacklisted_hashes
- {self.hash_choice}
)
)
msg = f"📘 **Reset Algorithm**: {author} reset "\ msg = (
f"algorithm in {channel.mention}" f"📘 **Reset Algorithm**: {author} reset " f"algorithm in {channel.mention}"
)
msg += f"\n💬 __Current algorithm__: {self.hash_choice.upper()}" msg += f"\n💬 __Current algorithm__: {self.hash_choice.upper()}"
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(msg) await log_channel.send(msg)
@ -82,8 +88,10 @@ class Verification(Cog):
async def reset(self, ctx, limit: int = 100, force: bool = False): async def reset(self, ctx, limit: int = 100, force: bool = False):
"""Wipes messages and pastes the welcome message again. Staff only.""" """Wipes messages and pastes the welcome message again. Staff only."""
if ctx.message.channel.id != config.welcome_channel and not force: if ctx.message.channel.id != config.welcome_channel and not force:
await ctx.send(f"This command is limited to" await ctx.send(
f" <#{config.welcome_channel}>, unless forced.") f"This command is limited to"
f" <#{config.welcome_channel}>, unless forced."
)
return return
await self.do_reset(ctx.channel, ctx.author.mention, limit) await self.do_reset(ctx.channel, ctx.author.mention, limit)
@ -92,8 +100,10 @@ class Verification(Cog):
async def resetalgo(self, ctx, limit: int = 100, force: bool = False): async def resetalgo(self, ctx, limit: int = 100, force: bool = False):
"""Resets the verification algorithm and does what reset does. Staff only.""" """Resets the verification algorithm and does what reset does. Staff only."""
if ctx.message.channel.id != config.welcome_channel and not force: if ctx.message.channel.id != config.welcome_channel and not force:
await ctx.send(f"This command is limited to" await ctx.send(
f" <#{config.welcome_channel}>, unless forced.") f"This command is limited to"
f" <#{config.welcome_channel}>, unless forced."
)
return return
await self.do_resetalgo(ctx.channel, ctx.author.mention, limit) await self.do_resetalgo(ctx.channel, ctx.author.mention, limit)
@ -113,13 +123,20 @@ class Verification(Cog):
mcl = message.content.lower() mcl = message.content.lower()
# Reply to users that insult the bot # Reply to users that insult the bot
oof = ["bad", "broken", "buggy", "bugged", oof = [
"stupid", "dumb", "silly", "fuck", "heck", "h*ck"] "bad",
"broken",
"buggy",
"bugged",
"stupid",
"dumb",
"silly",
"fuck",
"heck",
"h*ck",
]
if "bot" in mcl and any(insult in mcl for insult in oof): if "bot" in mcl and any(insult in mcl for insult in oof):
snark = random.choice(["bad human", snark = random.choice(["bad human", "no u", "no u, rtfm", "pebkac"])
"no u",
"no u, rtfm",
"pebkac"])
return await chan.send(snark) return await chan.send(snark)
# Get the role we will give in case of success # Get the role we will give in case of success
@ -127,38 +144,57 @@ class Verification(Cog):
# Get a list of stuff we'll allow and will consider close # Get a list of stuff we'll allow and will consider close
allowed_names = [f"@{full_name}", full_name, str(member.id)] allowed_names = [f"@{full_name}", full_name, str(member.id)]
close_names = [f"@{member.name}", member.name, discrim, close_names = [f"@{member.name}", member.name, discrim, f"#{discrim}"]
f"#{discrim}"]
# Now add the same things but with newlines at the end of them # Now add the same things but with newlines at the end of them
allowed_names += [(an + '\n') for an in allowed_names] allowed_names += [(an + "\n") for an in allowed_names]
close_names += [(cn + '\n') for cn in close_names] close_names += [(cn + "\n") for cn in close_names]
allowed_names += [(an + '\r\n') for an in allowed_names] allowed_names += [(an + "\r\n") for an in allowed_names]
close_names += [(cn + '\r\n') for cn in close_names] close_names += [(cn + "\r\n") for cn in close_names]
# [ ͡° ͜ᔦ ͡°] 𝐖𝐞𝐥𝐜𝐨𝐦𝐞 𝐭𝐨 𝐌𝐚𝐜 𝐎𝐒 𝟗. # [ ͡° ͜ᔦ ͡°] 𝐖𝐞𝐥𝐜𝐨𝐦𝐞 𝐭𝐨 𝐌𝐚𝐜 𝐎𝐒 𝟗.
allowed_names += [(an + '\r') for an in allowed_names] allowed_names += [(an + "\r") for an in allowed_names]
close_names += [(cn + '\r') for cn in close_names] close_names += [(cn + "\r") for cn in close_names]
# Finally, hash the stuff so that we can access them later :) # Finally, hash the stuff so that we can access them later :)
hash_allow = [hashlib.new(self.hash_choice, hash_allow = [
name.encode('utf-8')).hexdigest() hashlib.new(self.hash_choice, name.encode("utf-8")).hexdigest()
for name in allowed_names] for name in allowed_names
]
# I'm not even going to attempt to break those into lines jfc # I'm not even going to attempt to break those into lines jfc
if any(allow in mcl for allow in hash_allow): if any(allow in mcl for allow in hash_allow):
await member.add_roles(success_role) await member.add_roles(success_role)
return await chan.purge(limit=100, check=lambda m: m.author == message.author or (m.author == self.bot.user and message.author.mention in m.content)) return await chan.purge(
limit=100,
check=lambda m: m.author == message.author
or (
m.author == self.bot.user
and message.author.mention in m.content
),
)
# Detect if the user uses the wrong hash algorithm # Detect if the user uses the wrong hash algorithm
wrong_hash_algos = hashlib.algorithms_guaranteed - \ wrong_hash_algos = (
{self.hash_choice} - self.blacklisted_hashes hashlib.algorithms_guaranteed
- {self.hash_choice}
- self.blacklisted_hashes
)
for algo in wrong_hash_algos: for algo in wrong_hash_algos:
for name in itertools.chain(allowed_names, close_names): for name in itertools.chain(allowed_names, close_names):
if hashlib.new(algo, name.encode('utf-8')).hexdigest() in mcl: if hashlib.new(algo, name.encode("utf-8")).hexdigest() in mcl:
log_channel = self.bot.get_channel(config.log_channel) log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(f"User {message.author.mention} tried verification with algo {algo} instead of {self.hash_choice}.") await log_channel.send(
return await chan.send(f"{message.author.mention} :no_entry: Close, but not quite. Go back and re-read!") f"User {message.author.mention} tried verification with algo {algo} instead of {self.hash_choice}."
)
return await chan.send(
f"{message.author.mention} :no_entry: Close, but not quite. Go back and re-read!"
)
if full_name in message.content or str(member.id) in message.content or member.name in message.content or discrim in message.content: if (
full_name in message.content
or str(member.id) in message.content
or member.name in message.content
or discrim in message.content
):
no_text = ":no_entry: Incorrect. You need to do something *specific* with your name and discriminator instead of just posting it. Please re-read the rules carefully and look up any terms you are not familiar with." no_text = ":no_entry: Incorrect. You need to do something *specific* with your name and discriminator instead of just posting it. Please re-read the rules carefully and look up any terms you are not familiar with."
rand_num = random.randint(1, 100) rand_num = random.randint(1, 100)
if rand_num == 42: if rand_num == 42:
@ -166,7 +202,7 @@ class Verification(Cog):
elif rand_num == 43: elif rand_num == 43:
no_text = "ugh, wrong, read the rules." no_text = "ugh, wrong, read the rules."
elif rand_num == 44: elif rand_num == 44:
no_text = "\"The definition of insanity is doing the same thing over and over again, but expecting different results.\"\n-Albert Einstein" no_text = '"The definition of insanity is doing the same thing over and over again, but expecting different results."\n-Albert Einstein'
await chan.send(f"{message.author.mention} {no_text}") await chan.send(f"{message.author.mention} {no_text}")
@Cog.listener() @Cog.listener()

View file

@ -11,31 +11,35 @@ source_url = "https://github.com/reswitched/robocop-ng"
rules_url = "https://reswitched.team/discord/#rules" rules_url = "https://reswitched.team/discord/#rules"
# The bot description to be used in .robocop embed # The bot description to be used in .robocop embed
embed_desc = "Robocop-NG is developed by [Ave](https://github.com/aveao)"\ embed_desc = (
" and [tomGER](https://github.com/tumGER), and is a rewrite "\ "Robocop-NG is developed by [Ave](https://github.com/aveao)"
"of Robocop.\nRobocop is based on Kurisu by 916253 and ihaveamac." " and [tomGER](https://github.com/tumGER), and is a rewrite "
"of Robocop.\nRobocop is based on Kurisu by 916253 and ihaveamac."
)
# The cogs the bot will load on startup. # The cogs the bot will load on startup.
initial_cogs = ['cogs.common', initial_cogs = [
'cogs.admin', "cogs.common",
'cogs.verification', "cogs.admin",
'cogs.mod', "cogs.verification",
'cogs.mod_note', "cogs.mod",
'cogs.mod_reacts', "cogs.mod_note",
'cogs.mod_userlog', "cogs.mod_reacts",
'cogs.mod_timed', "cogs.mod_userlog",
'cogs.mod_watch', "cogs.mod_timed",
'cogs.basic', "cogs.mod_watch",
'cogs.logs', "cogs.basic",
'cogs.err', "cogs.logs",
'cogs.lockdown', "cogs.err",
'cogs.legacy', "cogs.lockdown",
'cogs.links', "cogs.legacy",
'cogs.remind', "cogs.links",
'cogs.robocronp', "cogs.remind",
'cogs.meme', "cogs.robocronp",
'cogs.invites'] "cogs.meme",
"cogs.invites",
]
# The following cogs are also available but aren't loaded by default: # The following cogs are also available but aren't loaded by default:
# cogs.imagemanip - Adds a meme command called .cox. # cogs.imagemanip - Adds a meme command called .cox.
@ -52,27 +56,27 @@ initial_cogs = ['cogs.common',
min_age = datetime.timedelta(minutes=15) min_age = datetime.timedelta(minutes=15)
# The bot will only work in these guilds # The bot will only work in these guilds
guild_whitelist = [ guild_whitelist = [269333940928512010] # ReSwitched discord
269333940928512010 # ReSwitched discord
]
# Named roles to be used with .approve and .revoke # Named roles to be used with .approve and .revoke
# Example: .approve User hacker # Example: .approve User hacker
named_roles = { named_roles = {
"community": 420010997877833731, "community": 420010997877833731,
"hacker": 364508795038072833, "hacker": 364508795038072833,
"participant": 434353085926866946 "participant": 434353085926866946,
} }
# The bot manager and staff roles # The bot manager and staff roles
# Bot manager can run eval, exit and other destructive commands # Bot manager can run eval, exit and other destructive commands
# Staff can run administrative commands # Staff can run administrative commands
bot_manager_role_id = 466447265863696394 # Bot management role in ReSwitched bot_manager_role_id = 466447265863696394 # Bot management role in ReSwitched
staff_role_ids = [364647829248933888, # Team role in ReSwitched staff_role_ids = [
360138431524765707, # Mod role in ReSwitched 364647829248933888, # Team role in ReSwitched
466447265863696394, # Bot management role in ReSwitched 360138431524765707, # Mod role in ReSwitched
360138163156549632, # Admin role in ReSwitched 466447265863696394, # Bot management role in ReSwitched
287289529986187266] # Wizard role in ReSwitched 360138163156549632, # Admin role in ReSwitched
287289529986187266,
] # Wizard role in ReSwitched
# Various log channels used to log bot and guild's activity # Various log channels used to log bot and guild's activity
# You can use same channel for multiple log types # You can use same channel for multiple log types
@ -86,29 +90,30 @@ welcome_channel = 326416669058662401 # newcomers channel in ReSwitched
# These channel entries are used to determine which roles will be given # These channel entries are used to determine which roles will be given
# access when we unmute on them # access when we unmute on them
general_channels = [420029476634886144, general_channels = [
414949821003202562, 420029476634886144,
383368936466546698, 414949821003202562,
343244421044633602, 383368936466546698,
491316901692178432, 343244421044633602,
539212260350885908] # Channels everyone can access 491316901692178432,
community_channels = [269333940928512010, 539212260350885908,
438839875970662400, ] # Channels everyone can access
404722395845361668, community_channels = [
435687501068501002, 269333940928512010,
286612533757083648] # Channels requiring community role 438839875970662400,
404722395845361668,
435687501068501002,
286612533757083648,
] # Channels requiring community role
# Controls which roles are blocked during lockdown # Controls which roles are blocked during lockdown
lockdown_configs = { lockdown_configs = {
# Used as a default value for channels without a config # Used as a default value for channels without a config
"default": { "default": {"channels": general_channels, "roles": [named_roles["participant"]]},
"channels": general_channels,
"roles": [named_roles["participant"]]
},
"community": { "community": {
"channels": community_channels, "channels": community_channels,
"roles": [named_roles["community"], named_roles["hacker"]] "roles": [named_roles["community"], named_roles["hacker"]],
} },
} }
# Mute role is applied to users when they're muted # Mute role is applied to users when they're muted
@ -135,24 +140,33 @@ list_files_channel = 0
list_channels = [] list_channels = []
# All lower case, no spaces, nothing non-alphanumeric # All lower case, no spaces, nothing non-alphanumeric
suspect_words = ["sx", "tx", "reinx", # piracy-enabling cfws suspect_words = [
"gomanx", # piracy-enabling cfws "sx",
"tinfoil", "dz", # title managers "tx",
"goldleaf", "lithium", # title managers "reinx", # piracy-enabling cfws
"cracked", # older term for pirated games "gomanx", # piracy-enabling cfws
"xci", "nsz"] # "backup" format "tinfoil",
"dz", # title managers
"goldleaf",
"lithium", # title managers
"cracked", # older term for pirated games
"xci",
"nsz",
] # "backup" format
# List of words that will be ignored if they match one of the # List of words that will be ignored if they match one of the
# suspect_words (This is used to remove false positives) # suspect_words (This is used to remove false positives)
suspect_ignored_words = ["excit", suspect_ignored_words = [
"s/x", "excit",
"3dsx", "s/x",
"psx", "3dsx",
"txt", "psx",
"s(x", "txt",
"txd", "s(x",
"t=x", "txd",
"osx"] "t=x",
"osx",
]
# == Only if you want to use cogs.pin == # == Only if you want to use cogs.pin ==
# Used for the pinboard. Leave empty if you don't wish for a gist pinboard. # Used for the pinboard. Leave empty if you don't wish for a gist pinboard.
@ -189,7 +203,6 @@ welcome_rules = (
Read all the rules before participating in chat. Not reading the rules is *not* an excuse for breaking them. Read all the rules before participating in chat. Not reading the rules is *not* an excuse for breaking them.
It's suggested that you read channel topics and pins before asking questions as well, as some questions may have already been answered in those. It's suggested that you read channel topics and pins before asking questions as well, as some questions may have already been answered in those.
""", """,
# 2 # 2
""" """
Be nice to each other. It's fine to disagree, it's not fine to insult or attack other people. Be nice to each other. It's fine to disagree, it's not fine to insult or attack other people.
@ -197,31 +210,24 @@ welcome_rules = (
Constant antagonistic behavior is considered uncivil and appropriate action will be taken. Constant antagonistic behavior is considered uncivil and appropriate action will be taken.
The use of derogatory slurs -- sexist, racist, homophobic, transphobic, or otherwise -- is unacceptable and may be grounds for an immediate ban. The use of derogatory slurs -- sexist, racist, homophobic, transphobic, or otherwise -- is unacceptable and may be grounds for an immediate ban.
""", """,
# 3 # 3
'If you have concerns about another user, please take up your concerns with a staff member (someone with the "mod" role in the sidebar) in private. Don\'t publicly call other users out.', 'If you have concerns about another user, please take up your concerns with a staff member (someone with the "mod" role in the sidebar) in private. Don\'t publicly call other users out.',
# 4 # 4
""" """
From time to time, we may mention everyone in the server. We do this when we feel something important is going on that requires attention. Complaining about these pings may result in a ban. From time to time, we may mention everyone in the server. We do this when we feel something important is going on that requires attention. Complaining about these pings may result in a ban.
To disable notifications for these pings, suppress them in "ReSwitched → Notification Settings". To disable notifications for these pings, suppress them in "ReSwitched → Notification Settings".
""", """,
# 5 # 5
""" """
Don't spam. Don't spam.
For excessively long text, use a service like <https://0bin.net/>. For excessively long text, use a service like <https://0bin.net/>.
""", """,
# 6 # 6
"Don't brigade, raid, or otherwise attack other people or communities. Don't discuss participation in these attacks. This may warrant an immediate permanent ban.", "Don't brigade, raid, or otherwise attack other people or communities. Don't discuss participation in these attacks. This may warrant an immediate permanent ban.",
# 7 # 7
'Off-topic content goes to #off-topic. Keep low-quality content like memes out.', "Off-topic content goes to #off-topic. Keep low-quality content like memes out.",
# 8 # 8
'Trying to evade, look for loopholes, or stay borderline within the rules will be treated as breaking them.', "Trying to evade, look for loopholes, or stay borderline within the rules will be treated as breaking them.",
# 9 # 9
""" """
Absolutely no piracy or related discussion. This includes: Absolutely no piracy or related discussion. This includes:
@ -231,16 +237,14 @@ welcome_rules = (
Usage of piracy-focused groups' (Team Xecuter, etc.) hardware and software, such as SX OS. Usage of piracy-focused groups' (Team Xecuter, etc.) hardware and software, such as SX OS.
This is a zero-tolerance, non-negotiable policy that is enforced strictly and swiftly, up to and including instant bans without warning. This is a zero-tolerance, non-negotiable policy that is enforced strictly and swiftly, up to and including instant bans without warning.
""", """,
# 10 # 10
'The first character of your server nickname should be alphanumeric if you wish to talk in chat.', "The first character of your server nickname should be alphanumeric if you wish to talk in chat.",
# 11 # 11
""" """
Do not boost the server. Do not boost the server.
ReSwitched neither wants nor needs your server boosts, and your money is better off elsewhere. Consider the EFF (or a charity of your choice). ReSwitched neither wants nor needs your server boosts, and your money is better off elsewhere. Consider the EFF (or a charity of your choice).
Boosting the server is liable to get you kicked (to remove the nitro boost role), and/or warned. Roles you possessed prior to the kick may not be restored in a timely fashion. Boosting the server is liable to get you kicked (to remove the nitro boost role), and/or warned. Roles you possessed prior to the kick may not be restored in a timely fashion.
""" """,
) )
@ -258,7 +262,6 @@ welcome_footer = (
#hack-n-all - General hacking, hardware and software development channel for hacking on things *other* than the switch. This is a great place to ask about hacking other systems-- and for the community to have technical discussions. #hack-n-all - General hacking, hardware and software development channel for hacking on things *other* than the switch. This is a great place to ask about hacking other systems-- and for the community to have technical discussions.
""", """,
""" """
#switch-hacking-general - Channel for everyone working on hacking the switch-- both in an exploit and a low-level hardware sense. This is where a lot of our in-the-open development goes on. Note that this isn't the place for developing homebrew-- we have #homebrew-development for that! #switch-hacking-general - Channel for everyone working on hacking the switch-- both in an exploit and a low-level hardware sense. This is where a lot of our in-the-open development goes on. Note that this isn't the place for developing homebrew-- we have #homebrew-development for that!

View file

@ -16,17 +16,19 @@ def check_if_bot_manager(ctx):
def check_if_staff_or_ot(ctx): def check_if_staff_or_ot(ctx):
if not ctx.guild: if not ctx.guild:
return True return True
is_ot = (ctx.channel.name == "off-topic") is_ot = ctx.channel.name == "off-topic"
is_bot_cmds = (ctx.channel.name == "bot-cmds") is_bot_cmds = ctx.channel.name == "bot-cmds"
is_staff = any(r.id in config.staff_role_ids for r in ctx.author.roles) is_staff = any(r.id in config.staff_role_ids for r in ctx.author.roles)
return (is_ot or is_staff or is_bot_cmds) return is_ot or is_staff or is_bot_cmds
def check_if_collaborator(ctx): def check_if_collaborator(ctx):
if not ctx.guild: if not ctx.guild:
return False return False
return any(r.id in config.staff_role_ids + config.allowed_pin_roles return any(
for r in ctx.author.roles) r.id in config.staff_role_ids + config.allowed_pin_roles
for r in ctx.author.roles
)
def check_if_pin_channel(ctx): def check_if_pin_channel(ctx):

View file

@ -99,24 +99,18 @@ switch_modules = {
212: "GRC ", 212: "GRC ",
216: "Migration ", 216: "Migration ",
217: "Migration Idc Server ", 217: "Migration Idc Server ",
# Libnx # Libnx
345: "libnx ", 345: "libnx ",
346: "Homebrew ABI ", 346: "Homebrew ABI ",
347: "Homebrew Loader ", 347: "Homebrew Loader ",
348: "libnx Nvidia", 348: "libnx Nvidia",
349: "libnx Binder", 349: "libnx Binder",
# Support Errors # Support Errors
800: "General web-applet", 800: "General web-applet",
809: "WifiWebAuthApplet", 809: "WifiWebAuthApplet",
810: "Whitelisted-applet", 810: "Whitelisted-applet",
811: "ShopN", 811: "ShopN",
# Custom Sysmodules # Custom Sysmodules
311: "SwitchPresence", 311: "SwitchPresence",
} }
@ -150,11 +144,11 @@ switch_known_errcodes = {
0xFC01: "Reserved value ", 0xFC01: "Reserved value ",
0xFE01: "Invalid hardware breakpoint ", 0xFE01: "Invalid hardware breakpoint ",
0x10001: "[Usermode] Fatal exception ", 0x10001: "[Usermode] Fatal exception ",
0x10201: "Last thread didn\'t belong to your process ", 0x10201: "Last thread didn't belong to your process ",
0x10601: "Port closed ", 0x10601: "Port closed ",
0x10801: "Resource limit exceeded ", 0x10801: "Resource limit exceeded ",
0x20801: "Command buffer too small ", 0x20801: "Command buffer too small ",
0x40a01: "Invalid process ID.", 0x40A01: "Invalid process ID.",
0x40C01: "Invalid thread ID.", 0x40C01: "Invalid thread ID.",
0x40E01: "Invalid thread ID (used in svcGetDebugThreadParam).", 0x40E01: "Invalid thread ID (used in svcGetDebugThreadParam).",
0x6402: "NCA is older than version 3, or NCA SDK version is older than 0.11.0.0", 0x6402: "NCA is older than version 3, or NCA SDK version is older than 0.11.0.0",
@ -248,7 +242,7 @@ switch_known_errcodes = {
0x3EA03: "Invalid handle ", 0x3EA03: "Invalid handle ",
0x3EE03: "Invalid memory mirror ", 0x3EE03: "Invalid memory mirror ",
0x7FE03: "TLS slot is not allocated ", 0x7FE03: "TLS slot is not allocated ",
0xA05: "NcaID not found. Returned when attempting to mount titles which exist that aren\'t *8XX titles, the same way *8XX titles are mounted. ", 0xA05: "NcaID not found. Returned when attempting to mount titles which exist that aren't *8XX titles, the same way *8XX titles are mounted. ",
0xE05: "TitleId not found ", 0xE05: "TitleId not found ",
0x1805: "Invalid StorageId ", 0x1805: "Invalid StorageId ",
0xDC05: "Gamecard not inserted ", 0xDC05: "Gamecard not inserted ",
@ -273,7 +267,7 @@ switch_known_errcodes = {
0xA09: "Invalid files. ", 0xA09: "Invalid files. ",
0xE09: "Already registered. ", 0xE09: "Already registered. ",
0x1009: "Title not found. ", 0x1009: "Title not found. ",
0x1209: "Title-id in ACI0 doesn\'t match range in ACID. ", 0x1209: "Title-id in ACI0 doesn't match range in ACID. ",
0x6609: "Invalid memory state/permission ", 0x6609: "Invalid memory state/permission ",
0x6A09: "Invalid NRR ", 0x6A09: "Invalid NRR ",
0xA209: "Unaligned NRR address ", 0xA209: "Unaligned NRR address ",
@ -281,15 +275,15 @@ switch_known_errcodes = {
0xAA09: "Bad NRR address ", 0xAA09: "Bad NRR address ",
0xAE09: "Bad initialization ", 0xAE09: "Bad initialization ",
0xC809: "Unknown ACI0 descriptor ", 0xC809: "Unknown ACI0 descriptor ",
0xCE09: "ACID/ACI0 don\'t match for descriptor KernelFlags ", 0xCE09: "ACID/ACI0 don't match for descriptor KernelFlags ",
0xD009: "ACID/ACI0 don\'t match for descriptor SyscallMask ", 0xD009: "ACID/ACI0 don't match for descriptor SyscallMask ",
0xD409: "ACID/ACI0 don\'t match for descriptor MapIoOrNormalRange ", 0xD409: "ACID/ACI0 don't match for descriptor MapIoOrNormalRange ",
0xD609: "ACID/ACI0 don\'t match for descriptor MapNormalPage ", 0xD609: "ACID/ACI0 don't match for descriptor MapNormalPage ",
0xDE09: "ACID/ACI0 don\'t match for descriptor InterruptPair ", 0xDE09: "ACID/ACI0 don't match for descriptor InterruptPair ",
0xE209: "ACID/ACI0 don\'t match for descriptor ApplicationType ", 0xE209: "ACID/ACI0 don't match for descriptor ApplicationType ",
0xE409: "ACID/ACI0 don\'t match for descriptor KernelReleaseVersion ", 0xE409: "ACID/ACI0 don't match for descriptor KernelReleaseVersion ",
0xE609: "ACID/ACI0 don\'t match for descriptor HandleTableSize ", 0xE609: "ACID/ACI0 don't match for descriptor HandleTableSize ",
0xE809: "ACID/ACI0 don\'t match for descriptor DebugFlags ", 0xE809: "ACID/ACI0 don't match for descriptor DebugFlags ",
0x1940A: "Invalid CMIF header size. ", 0x1940A: "Invalid CMIF header size. ",
0x1A60A: "Invalid CMIF input header. ", 0x1A60A: "Invalid CMIF input header. ",
0x1A80A: "Invalid CMIF output header. ", 0x1A80A: "Invalid CMIF output header. ",
@ -300,7 +294,7 @@ switch_known_errcodes = {
0x20B: "Unsupported operation ", 0x20B: "Unsupported operation ",
0xCC0B: "Out of server session memory ", 0xCC0B: "Out of server session memory ",
0x11A0B: "Went past maximum during marshalling. ", 0x11A0B: "Went past maximum during marshalling. ",
0x1900B: "Session doesn\'t support domains. ", 0x1900B: "Session doesn't support domains. ",
0x25A0B: "Remote process is dead. ", 0x25A0B: "Remote process is dead. ",
0x3260B: "Unknown request type ", 0x3260B: "Unknown request type ",
0x3D60B: "IPC Query 1 failed. ", 0x3D60B: "IPC Query 1 failed. ",
@ -343,8 +337,8 @@ switch_known_errcodes = {
0x1BC69: "Empty settings item key ", 0x1BC69: "Empty settings item key ",
0x1E269: "Setting group name is too long (64 character limit?) ", 0x1E269: "Setting group name is too long (64 character limit?) ",
0x1E469: "Setting name is too long (64 character limit?) ", 0x1E469: "Setting name is too long (64 character limit?) ",
0x20A69: "Setting group name ends with \'.\' or contains invalid characters (allowed: [a-z0-9_\-.]) ", 0x20A69: "Setting group name ends with '.' or contains invalid characters (allowed: [a-z0-9_\-.]) ",
0x20C69: "Setting name ends with \'.\' or contains invalid characters (allowed: [a-z0-9_\-.]) ", 0x20C69: "Setting name ends with '.' or contains invalid characters (allowed: [a-z0-9_\-.]) ",
0x4DA69: "Null language code buffer ", 0x4DA69: "Null language code buffer ",
0x4EE69: "Null network settings buffer ", 0x4EE69: "Null network settings buffer ",
0x4F069: "Null network settings output count buffer ", 0x4F069: "Null network settings output count buffer ",
@ -469,7 +463,7 @@ switch_known_errcodes = {
0xD48C: "Invalid descriptor ", 0xD48C: "Invalid descriptor ",
0x1928C: "USB device not bound / interface already enabled ", 0x1928C: "USB device not bound / interface already enabled ",
0x299: "Invalid audio device ", 0x299: "Invalid audio device ",
0x499: "Operation couldn\'t complete successfully ", 0x499: "Operation couldn't complete successfully ",
0x699: "Invalid sample rate ", 0x699: "Invalid sample rate ",
0x899: "Buffer size too small ", 0x899: "Buffer size too small ",
0x1099: "Too many buffers are still unreleased ", 0x1099: "Too many buffers are still unreleased ",
@ -486,7 +480,7 @@ switch_known_errcodes = {
0xF0CD: "IR image data not available/ready. ", 0xF0CD: "IR image data not available/ready. ",
0x35B: "Failed to init SM. ", 0x35B: "Failed to init SM. ",
0x55B: "Failed to init FS. ", 0x55B: "Failed to init FS. ",
0x75B: "Failed to to open NRO file. May also happen when SD card isn\'t inserted / SD mounting failed earlier. ", 0x75B: "Failed to to open NRO file. May also happen when SD card isn't inserted / SD mounting failed earlier. ",
0x95B: "Failed to read NRO header. ", 0x95B: "Failed to read NRO header. ",
0xB5B: "Invalid NRO magic. ", 0xB5B: "Invalid NRO magic. ",
0xD5B: "Invalid NRO segments. ", 0xD5B: "Invalid NRO segments. ",
@ -508,7 +502,7 @@ switch_known_errcodes = {
0x480: "Storage not available.", 0x480: "Storage not available.",
0x1987E: "Development/debug-only behavior", 0x1987E: "Development/debug-only behavior",
0xD27E: "Invalid database entry count", 0xD27E: "Invalid database entry count",
0xCE7E: "Invalid database signature value (should be \"NFDB\")", 0xCE7E: 'Invalid database signature value (should be "NFDB")',
0x87E: "Entry not found", 0x87E: "Entry not found",
0x27E: "Invalid argument", 0x27E: "Invalid argument",
0x7BC74: "Unimplemented functionality", 0x7BC74: "Unimplemented functionality",
@ -565,15 +559,12 @@ switch_known_errcodes = {
0xC47A: "Invalid operation", 0xC47A: "Invalid operation",
0x290: "Exited Abnormally ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Abormal)", 0x290: "Exited Abnormally ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Abormal)",
0x690: "Canceled ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Canceled)", 0x690: "Canceled ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Canceled)",
0x890: "Rejected", #me_irl 0x890: "Rejected", # me_irl
0xA90: "Exited Unexpectedly ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Unexpected)", 0xA90: "Exited Unexpectedly ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Unexpected)",
0x58ACA: "Npad ID is out of range.", 0x58ACA: "Npad ID is out of range.",
0x1A8CD: "IR camera handle pointer is null.", 0x1A8CD: "IR camera handle pointer is null.",
0x198CD: "IR camera invalid handle value.", 0x198CD: "IR camera invalid handle value.",
# FS Codes # FS Codes
0xD401: "Error: Passed buffer is not usable for fs library. ", 0xD401: "Error: Passed buffer is not usable for fs library. ",
0x177A02: "Error: Specified value is out of range. ", 0x177A02: "Error: Specified value is out of range. ",
0x2F5C02: "Error: Invalid size was specified.", 0x2F5C02: "Error: Invalid size was specified.",
@ -582,18 +573,14 @@ switch_known_errcodes = {
0x307202: "Error: OpenMode_AllowAppend is required for implicit extension of file size by WriteFile(). ", 0x307202: "Error: OpenMode_AllowAppend is required for implicit extension of file size by WriteFile(). ",
0x346402: "Error: Enough journal space is not left. ", 0x346402: "Error: Enough journal space is not left. ",
0x346A02: "Error: The open count of files and directories reached the limitation. ", 0x346A02: "Error: The open count of files and directories reached the limitation. ",
# Fatal # Fatal
0x4A2: "Can be triggered by running svcBreak. The svcBreak params have no affect on the value of the thrown error-code.", 0x4A2: "Can be triggered by running svcBreak. The svcBreak params have no affect on the value of the thrown error-code.",
0xA8: "Userland ARM undefined instruction exception", 0xA8: "Userland ARM undefined instruction exception",
0x2A8: "Userland ARM prefetch-abort due to PC set to non-executable region", 0x2A8: "Userland ARM prefetch-abort due to PC set to non-executable region",
0x4A8: "Userland ARM data abort. Also caused by abnormal process termination via svcExitProcess. Note: directly jumping to nnMain()-retaddr from non-main-thread has the same result.", 0x4A8: "Userland ARM data abort. Also caused by abnormal process termination via svcExitProcess. Note: directly jumping to nnMain()-retaddr from non-main-thread has the same result.",
0x6A8: "Userland PC address not aligned to 4 bytes ", 0x6A8: "Userland PC address not aligned to 4 bytes ",
0x10A8: "Can occur when attempting to call an svc outside the whitelist ", 0x10A8: "Can occur when attempting to call an svc outside the whitelist ",
# Libnx Errors from libnx/result.h # Libnx Errors from libnx/result.h
# - Normal - # - Normal -
0x359: "LibnxError_BadReloc", 0x359: "LibnxError_BadReloc",
0x559: "LibnxError_OutOfMemory", 0x559: "LibnxError_OutOfMemory",
@ -641,7 +628,6 @@ switch_known_errcodes = {
0x5959: "LibnxError_NvinfoFailedToInitialize", 0x5959: "LibnxError_NvinfoFailedToInitialize",
0x5B59: "LibnxError_NvbufFailedToInitialize", 0x5B59: "LibnxError_NvbufFailedToInitialize",
0x5D59: "LibnxError_LibAppletBadExit", 0x5D59: "LibnxError_LibAppletBadExit",
# - Libnx Binder - # - Libnx Binder -
0x35D: "LibnxBinderError_Unknown", 0x35D: "LibnxBinderError_Unknown",
0x55D: "LibnxBinderError_NoMemory", 0x55D: "LibnxBinderError_NoMemory",
@ -660,7 +646,6 @@ switch_known_errcodes = {
0x1F5D: "LibnxBinderError_TimedOut", 0x1F5D: "LibnxBinderError_TimedOut",
0x215D: "LibnxBinderError_UnknownTransaction", 0x215D: "LibnxBinderError_UnknownTransaction",
0x235D: "LibnxBinderError_FdsNotAllowed", 0x235D: "LibnxBinderError_FdsNotAllowed",
# - LibNX Nvidia - # - LibNX Nvidia -
0x35C: "LibnxNvidiaError_Unknown", 0x35C: "LibnxNvidiaError_Unknown",
0x55C: "LibnxNvidiaError_NotImplemented", 0x55C: "LibnxNvidiaError_NotImplemented",
@ -681,91 +666,81 @@ switch_known_errcodes = {
0x235C: "LibnxNvidiaError_SharedMemoryTooSmall", 0x235C: "LibnxNvidiaError_SharedMemoryTooSmall",
0x255C: "LibnxNvidiaError_FileOperationFailed", 0x255C: "LibnxNvidiaError_FileOperationFailed",
0x275C: "LibnxNvidiaError_IoctlFailed", 0x275C: "LibnxNvidiaError_IoctlFailed",
# Non-SwitchBrew Error Codes - Should probably add them to SwitchBrew if you read this # Non-SwitchBrew Error Codes - Should probably add them to SwitchBrew if you read this
0x7E12B: "Eshop connection failed",
0x7E12B: 'Eshop connection failed', 0x39D689: "CDN Ban",
0x39D689: 'CDN Ban', 0x3E8E7C: "Error in account login/creation",
0x3E8E7C: 'Error in account login/creation', 0x3E8EA0: "Failed connection test",
0x3E8EA0: 'Failed connection test', 0x1F4E7C: "(normal) console ban",
0x1F4E7C: '(normal) console ban', 0x27EE7C: "(potential) complete account ban", # This error is still super new, needs more informations
0x27EE7C: '(potential) complete account ban', # This error is still super new, needs more informations
0x36B72B: "Access token expired", 0x36B72B: "Access token expired",
0x1F486E: "Internet connection lost because the console entered sleep mode.", 0x1F486E: "Internet connection lost because the console entered sleep mode.",
0x21C89: "Failed to base64-encode the EticketDeviceCertificate during an attempted AccountGetDynamicEtickets (personalized ticket) request to ecs.", 0x21C89: "Failed to base64-encode the EticketDeviceCertificate during an attempted AccountGetDynamicEtickets (personalized ticket) request to ecs.",
0x5089: "Failed to snprintf the AccountGetDynamicEtickets (personalized ticket) request JSON data.", 0x5089: "Failed to snprintf the AccountGetDynamicEtickets (personalized ticket) request JSON data.",
0x6410: "GetApplicationControlData: unable to find control for the input title ID", 0x6410: "GetApplicationControlData: unable to find control for the input title ID",
0xa073: "NFC is disabled", 0xA073: "NFC is disabled",
0x16473: "Could not mount tag (invalid tag type?)", 0x16473: "Could not mount tag (invalid tag type?)",
0x8073: "Device unavailable", 0x8073: "Device unavailable",
0x10073: "App area not found", 0x10073: "App area not found",
0x11073: "Tag corrupted?", 0x11073: "Tag corrupted?",
0xc880: "thrown by AM when qlaunch is terminated", 0xC880: "thrown by AM when qlaunch is terminated",
0xc87c: "invalid user", 0xC87C: "invalid user",
0xc7e: "mii already exists", 0xC7E: "mii already exists",
0xa7e: "full database", 0xA7E: "full database",
0x87e: "mii not found", 0x87E: "mii not found",
0x115b: "[HBL] Stopped loading NROs", 0x115B: "[HBL] Stopped loading NROs",
0x48c69: "device_cert_ecc_b223 failed to load", 0x48C69: "device_cert_ecc_b223 failed to load",
0x138E02: "gamecard cmd buffer too small - must be 0x40 (or bigger)", 0x138E02: "gamecard cmd buffer too small - must be 0x40 (or bigger)",
0x138E02: "gc out of bounds sector access", 0x138E02: "gc out of bounds sector access",
0x13DC02: "gc sector start is out of range for partition 1", 0x13DC02: "gc sector start is out of range for partition 1",
0x13D802: "gc sector end out of range for partition 1", 0x13D802: "gc sector end out of range for partition 1",
0x13DA02: "gc sector wrong partition access", 0x13DA02: "gc sector wrong partition access",
# 0x3E8E89: 'Failed to access Firmware Updates - Often because of DNS!', # 0x3E8E89: 'Failed to access Firmware Updates - Often because of DNS!',
# ^ Also used by libcurl # ^ Also used by libcurl
# Atmosphere # Atmosphere
0xCAFEF: "Atmosphere: Version Mismatch", 0xCAFEF: "Atmosphere: Version Mismatch",
# Pegaswitch # Pegaswitch
0xA7200: "Fake-Error by Pegaswitch",
0xa7200: "Fake-Error by Pegaswitch",
# SwitchPresence # SwitchPresence
#Archived because the plugin got discontinued # Archived because the plugin got discontinued
#0x337: "Error_InitSocket", # 0x337: "Error_InitSocket",
#0x537: "Error_Listen", # 0x537: "Error_Listen",
#0x737: "Error_Accepting", # 0x737: "Error_Accepting",
#0x937: "Error_ListAppFailed", # 0x937: "Error_ListAppFailed",
# 0xb37: "Error_InvalidMagic", # 0xb37: "Error_InvalidMagic",
#0xd37: "Error_CmdIdNotConfirm", # 0xd37: "Error_CmdIdNotConfirm",
#0xf37: "Error_CmdIdNotSendBuff", # 0xf37: "Error_CmdIdNotSendBuff",
#0x1137: "Error_RecData", # 0x1137: "Error_RecData",
#0x1337: "Error_SendData", # 0x1337: "Error_SendData",
#0x1537: "Error_InitNS", # 0x1537: "Error_InitNS",
#0x1737: "Error_InitACC", # 0x1737: "Error_InitACC",
#0x1937: "Error_GetControlData", # 0x1937: "Error_GetControlData",
#0x1b37: "Error_InvalidControlSize", # 0x1b37: "Error_InvalidControlSize",
#0x1d37: "Error_GetAciveUser", # 0x1d37: "Error_GetAciveUser",
#0x1f37: "Error_GetProfile", # 0x1f37: "Error_GetProfile",
#0x2137: "Error_ProfileGet", # 0x2137: "Error_ProfileGet",
#0x2337: "Error_InitPMDMNT", # 0x2337: "Error_InitPMDMNT",
#0x2537: "Error_GetAppPid", # 0x2537: "Error_GetAppPid",
#0x2737: "Error_GetProcessTid", # 0x2737: "Error_GetProcessTid",
#0x2937: "Error_InitPMINFO", # 0x2937: "Error_InitPMINFO",
#0x2b37: "Error_GetPidList", # 0x2b37: "Error_GetPidList",
#0x2d37: "Error_GetDebugProc", # 0x2d37: "Error_GetDebugProc",
#0x2f37: "Error_CloseHandle", # 0x2f37: "Error_CloseHandle",
# Joke # Joke
0xDEADBEEF: "Congrats, you found some hexspeak \n \n https://www.youtube.com/watch?v=DLzxrzFCyOs", 0xDEADBEEF: "Congrats, you found some hexspeak \n \n https://www.youtube.com/watch?v=DLzxrzFCyOs",
# By Ave # By Ave
0x0: "Happens in various situations, not necessarily an error, but still prevents booting.\n\nIf you got this because you downgraded, it's because you downgraded between major/key versions (7.0.x -> 6.2.0, 6.2.0 -> 6.1.0 etc) without console initialization (deleting system save files).\n\nTo recover from that: Delete system all save files except 80...120. Keep in mind that this will effectively be a factory reset.", 0x0: "Happens in various situations, not necessarily an error, but still prevents booting.\n\nIf you got this because you downgraded, it's because you downgraded between major/key versions (7.0.x -> 6.2.0, 6.2.0 -> 6.1.0 etc) without console initialization (deleting system save files).\n\nTo recover from that: Delete system all save files except 80...120. Keep in mind that this will effectively be a factory reset.",
} }
switch_known_errcode_ranges = { switch_known_errcode_ranges = {
# NIM # NIM
137: [ 137: [
[8001, 8096, 'libcurl error 1-96. Some of the libcurl errors in the error-table map to the above unknown-libcurl-error however.'], [
8001,
8096,
"libcurl error 1-96. Some of the libcurl errors in the error-table map to the above unknown-libcurl-error however.",
],
], ],
# FS # FS
2: [ 2: [
[2000, 2499, "Error: Failed to access SD card."], [2000, 2499, "Error: Failed to access SD card."],
@ -786,12 +761,19 @@ switch_known_errcode_ranges = {
[6300, 6399, "Error: Unsupported operation."], [6300, 6399, "Error: Unsupported operation."],
[6400, 6499, "Error: Permission denied."], [6400, 6499, "Error: Permission denied."],
], ],
# NIFM Support Page Links # NIFM Support Page Links
110: [ 110: [
[2900, 2999, "https://en-americas-support.nintendo.com/app/answers/detail/a_id/22277/p/897"], [
[2000, 2899, "https://en-americas-support.nintendo.com/app/answers/detail/a_id/22263/p/897"], 2900,
] 2999,
"https://en-americas-support.nintendo.com/app/answers/detail/a_id/22277/p/897",
],
[
2000,
2899,
"https://en-americas-support.nintendo.com/app/answers/detail/a_id/22263/p/897",
],
],
} }
# Game Erros - Strings because Nintendo decided that it would be useless to put them into normal ints ;^) # Game Erros - Strings because Nintendo decided that it would be useless to put them into normal ints ;^)
@ -799,7 +781,6 @@ switch_known_errcode_ranges = {
switch_game_err = { switch_game_err = {
# Splatoon 2 # Splatoon 2
"2-AAB6A-3400": "Splatoon 2: A kick from online due to exefs edits.", "2-AAB6A-3400": "Splatoon 2: A kick from online due to exefs edits.",
# Youtube # Youtube
"2-ARVHA-0000": "Youtube: Unknown Error", "2-ARVHA-0000": "Youtube: Unknown Error",
} }
@ -847,272 +828,271 @@ switch_support_page = {
} }
dds_summaries = { dds_summaries = {
0: 'Success', 0: "Success",
1: 'Nothing happened', 1: "Nothing happened",
2: 'Would block', 2: "Would block",
3: 'Out of resource', 3: "Out of resource",
4: 'Not found', 4: "Not found",
5: 'Invalid state', 5: "Invalid state",
6: 'Not supported', 6: "Not supported",
7: 'Invalid argument', 7: "Invalid argument",
8: 'Wrong argument', 8: "Wrong argument",
9: 'Canceled', 9: "Canceled",
10: 'Status changed', 10: "Status changed",
11: 'Internal', 11: "Internal",
63: 'Invalid result value' 63: "Invalid result value",
} }
dds_levels = { dds_levels = {
0: "Success", 0: "Success",
1: "Info", 1: "Info",
25: "Status", 25: "Status",
26: "Temporary", 26: "Temporary",
27: "Permanent", 27: "Permanent",
28: "Usage", 28: "Usage",
29: "Reinitialize", 29: "Reinitialize",
30: "Reset", 30: "Reset",
31: "Fatal" 31: "Fatal",
} }
dds_modules = { dds_modules = {
0: 'Common', 0: "Common",
1: 'Kernel', 1: "Kernel",
2: 'Util', 2: "Util",
3: 'File server', 3: "File server",
4: 'Loader server', 4: "Loader server",
5: 'TCB', 5: "TCB",
6: 'OS', 6: "OS",
7: 'DBG', 7: "DBG",
8: 'DMNT', 8: "DMNT",
9: 'PDN', 9: "PDN",
10: 'GSP', 10: "GSP",
11: 'I2C', 11: "I2C",
12: 'GPIO', 12: "GPIO",
13: 'DD', 13: "DD",
14: 'CODEC', 14: "CODEC",
15: 'SPI', 15: "SPI",
16: 'PXI', 16: "PXI",
17: 'FS', 17: "FS",
18: 'DI', 18: "DI",
19: 'HID', 19: "HID",
20: 'CAM', 20: "CAM",
21: 'PI', 21: "PI",
22: 'PM', 22: "PM",
23: 'PM_LOW', 23: "PM_LOW",
24: 'FSI', 24: "FSI",
25: 'SRV', 25: "SRV",
26: 'NDM', 26: "NDM",
27: 'NWM', 27: "NWM",
28: 'SOC', 28: "SOC",
29: 'LDR', 29: "LDR",
30: 'ACC', 30: "ACC",
31: 'RomFS', 31: "RomFS",
32: 'AM', 32: "AM",
33: 'HIO', 33: "HIO",
34: 'Updater', 34: "Updater",
35: 'MIC', 35: "MIC",
36: 'FND', 36: "FND",
37: 'MP', 37: "MP",
38: 'MPWL', 38: "MPWL",
39: 'AC', 39: "AC",
40: 'HTTP', 40: "HTTP",
41: 'DSP', 41: "DSP",
42: 'SND', 42: "SND",
43: 'DLP', 43: "DLP",
44: 'HIO_LOW', 44: "HIO_LOW",
45: 'CSND', 45: "CSND",
46: 'SSL', 46: "SSL",
47: 'AM_LOW', 47: "AM_LOW",
48: 'NEX', 48: "NEX",
49: 'Friends', 49: "Friends",
50: 'RDT', 50: "RDT",
51: 'Applet', 51: "Applet",
52: 'NIM', 52: "NIM",
53: 'PTM', 53: "PTM",
54: 'MIDI', 54: "MIDI",
55: 'MC', 55: "MC",
56: 'SWC', 56: "SWC",
57: 'FatFS', 57: "FatFS",
58: 'NGC', 58: "NGC",
59: 'CARD', 59: "CARD",
60: 'CARDNOR', 60: "CARDNOR",
61: 'SDMC', 61: "SDMC",
62: 'BOSS', 62: "BOSS",
63: 'DBM', 63: "DBM",
64: 'Config', 64: "Config",
65: 'PS', 65: "PS",
66: 'CEC', 66: "CEC",
67: 'IR', 67: "IR",
68: 'UDS', 68: "UDS",
69: 'PL', 69: "PL",
70: 'CUP', 70: "CUP",
71: 'Gyroscope', 71: "Gyroscope",
72: 'MCU', 72: "MCU",
73: 'NS', 73: "NS",
74: 'News', 74: "News",
75: 'RO', 75: "RO",
76: 'GD', 76: "GD",
77: 'Card SPI', 77: "Card SPI",
78: 'EC', 78: "EC",
79: 'Web Browser', 79: "Web Browser",
80: 'Test', 80: "Test",
81: 'ENC', 81: "ENC",
82: 'PIA', 82: "PIA",
83: 'ACT', 83: "ACT",
84: 'VCTL', 84: "VCTL",
85: 'OLV', 85: "OLV",
86: 'NEIA', 86: "NEIA",
87: 'NPNS', 87: "NPNS",
90: 'AVD', 90: "AVD",
91: 'L2B', 91: "L2B",
92: 'MVD', 92: "MVD",
93: 'NFC', 93: "NFC",
94: 'UART', 94: "UART",
95: 'SPM', 95: "SPM",
96: 'QTM', 96: "QTM",
97: 'NFP (amiibo)', 97: "NFP (amiibo)",
254: 'Application', 254: "Application",
255: 'Invalid result value' 255: "Invalid result value",
} }
dds_descriptions = { dds_descriptions = {
0: 'Success', 0: "Success",
2: 'Invalid memory permissions (kernel)', 2: "Invalid memory permissions (kernel)",
4: 'Invalid ticket version (AM)', 4: "Invalid ticket version (AM)",
5: 'Invalid string length. This error is returned when service name length is greater than 8 or zero. (srv)', 5: "Invalid string length. This error is returned when service name length is greater than 8 or zero. (srv)",
6: 'Access denied. This error is returned when you request a service that you don\'t have access to. (srv)', 6: "Access denied. This error is returned when you request a service that you don't have access to. (srv)",
7: 'String size does not match string contents. This error is returned when service name contains an unexpected null byte. (srv)', 7: "String size does not match string contents. This error is returned when service name contains an unexpected null byte. (srv)",
8: 'Camera already in use/busy (qtm).', 8: "Camera already in use/busy (qtm).",
10: 'Not enough memory (os)', 10: "Not enough memory (os)",
26: 'Session closed by remote (os)', 26: "Session closed by remote (os)",
32: 'Empty CIA? (AM)', 32: "Empty CIA? (AM)",
37: 'Invalid NCCH? (AM)', 37: "Invalid NCCH? (AM)",
39: 'Invalid title version (AM)', 39: "Invalid title version (AM)",
43: 'Database doesn\'t exist/failed to open (AM)', 43: "Database doesn't exist/failed to open (AM)",
44: 'Trying to uninstall system-app (AM)', 44: "Trying to uninstall system-app (AM)",
47: 'Invalid command header (OS)', 47: "Invalid command header (OS)",
101: 'Archive not mounted/mount-point not found (fs)', 101: "Archive not mounted/mount-point not found (fs)",
105: 'Request timed out (http)', 105: "Request timed out (http)",
106: 'Invalid signature/CIA? (AM)', 106: "Invalid signature/CIA? (AM)",
120: 'Title/object not found? (fs)', 120: "Title/object not found? (fs)",
141: 'Gamecard not inserted? (fs)', 141: "Gamecard not inserted? (fs)",
190: 'Object does already exist/failed to create object.', 190: "Object does already exist/failed to create object.",
230: 'Invalid open-flags / permissions? (fs)', 230: "Invalid open-flags / permissions? (fs)",
250: 'FAT operation denied (fs?)', 250: "FAT operation denied (fs?)",
271: 'Invalid configuration (mvd).', 271: "Invalid configuration (mvd).",
335: '(No permission? Seemed to appear when JKSM was being used without its XML.)', 335: "(No permission? Seemed to appear when JKSM was being used without its XML.)",
391: 'NCCH hash-check failed? (fs)', 391: "NCCH hash-check failed? (fs)",
392: 'RSA/AES-MAC verification failed? (fs)', 392: "RSA/AES-MAC verification failed? (fs)",
393: 'Invalid database? (AM)', 393: "Invalid database? (AM)",
395: 'RomFS/Savedata hash-check failed? (fs)', 395: "RomFS/Savedata hash-check failed? (fs)",
630: 'Command not allowed / missing permissions? (fs)', 630: "Command not allowed / missing permissions? (fs)",
702: 'Invalid path? (fs)', 702: "Invalid path? (fs)",
740: '(Occurred when NDS card was inserted and attempting to use AM_GetTitleCount on MEDIATYPE_GAME_CARD.) (fs)', 740: "(Occurred when NDS card was inserted and attempting to use AM_GetTitleCount on MEDIATYPE_GAME_CARD.) (fs)",
761: 'Incorrect read-size for ExeFS? (fs)', 761: "Incorrect read-size for ExeFS? (fs)",
1000: 'Invalid selection', 1000: "Invalid selection",
1001: 'Too large', 1001: "Too large",
1002: 'Not authorized', 1002: "Not authorized",
1003: 'Already done', 1003: "Already done",
1004: 'Invalid size', 1004: "Invalid size",
1005: 'Invalid enum value', 1005: "Invalid enum value",
1006: 'Invalid combination', 1006: "Invalid combination",
1007: 'No data', 1007: "No data",
1008: 'Busy', 1008: "Busy",
1009: 'Misaligned address', 1009: "Misaligned address",
1010: 'Misaligned size', 1010: "Misaligned size",
1011: 'Out of memory', 1011: "Out of memory",
1012: 'Not implemented', 1012: "Not implemented",
1013: 'Invalid address', 1013: "Invalid address",
1014: 'Invalid pointer', 1014: "Invalid pointer",
1015: 'Invalid handle', 1015: "Invalid handle",
1016: 'Not initialized', 1016: "Not initialized",
1017: 'Already initialized', 1017: "Already initialized",
1018: 'Not found', 1018: "Not found",
1019: 'Cancel requested', 1019: "Cancel requested",
1020: 'Already exists', 1020: "Already exists",
1021: 'Out of range', 1021: "Out of range",
1022: 'Timeout', 1022: "Timeout",
1023: 'Invalid result value' 1023: "Invalid result value",
} }
# Nintendo Error Codes # Nintendo Error Codes
dds_errcodes = { dds_errcodes = {
# Nintendo 3DS # Nintendo 3DS
'001-0502': 'Some sort of network error related to friend presence. "Allow Friends to see your online status" might fix this.', "001-0502": 'Some sort of network error related to friend presence. "Allow Friends to see your online status" might fix this.',
'001-0803': 'Could not communicate with authentication server.', "001-0803": "Could not communicate with authentication server.",
'002-0102': 'System is permanently banned by Nintendo. ', "002-0102": "System is permanently banned by Nintendo. ",
'002-0107': 'System is temporarily(?) banned by Nintendo. ', "002-0107": "System is temporarily(?) banned by Nintendo. ",
'002-0119': 'System update required (outdated friends-module)', "002-0119": "System update required (outdated friends-module)",
'002-0120': 'Title update required (outdated title version)', "002-0120": "Title update required (outdated title version)",
'002-0121': 'Local friend code SEED has invalid signature.\n\nThis should not happen unless it is modified. The only use case for modifying this file is for system unbanning, so ', "002-0121": "Local friend code SEED has invalid signature.\n\nThis should not happen unless it is modified. The only use case for modifying this file is for system unbanning, so ",
'002-0123': 'System is generally banned by Nintendo. ', "002-0123": "System is generally banned by Nintendo. ",
'003-1099': 'Access point could not be found with the given SSID.', "003-1099": "Access point could not be found with the given SSID.",
'003-2001': 'DNS error. If using a custom DNS server, make sure the settings are correct.', "003-2001": "DNS error. If using a custom DNS server, make sure the settings are correct.",
'005-4800': 'HTTP Status 500 (Internal Error), unknown cause(?). eShop servers might have issues.', "005-4800": "HTTP Status 500 (Internal Error), unknown cause(?). eShop servers might have issues.",
'005-5602': 'Unable to connect to the eShop. This error is most likely the result of an incorrect region setting.\nMake sure your region is correctly set in System Settings. If you encounter this error after region-changing your system, make sure you followed all the steps properly.', "005-5602": "Unable to connect to the eShop. This error is most likely the result of an incorrect region setting.\nMake sure your region is correctly set in System Settings. If you encounter this error after region-changing your system, make sure you followed all the steps properly.",
'005-5964': 'Your Nintendo Network ID has been banned from accessing the eShop.\nIf you think this was unwarranted, you will have to contact Nintendo Support to have it reversed.', "005-5964": "Your Nintendo Network ID has been banned from accessing the eShop.\nIf you think this was unwarranted, you will have to contact Nintendo Support to have it reversed.",
'005-7550': 'Replace SD card(?). Occurs on Nintendo eShop.', "005-7550": "Replace SD card(?). Occurs on Nintendo eShop.",
'006-0102': 'Unexpected error. Could probably happen trying to play an out-of-region title online?', "006-0102": "Unexpected error. Could probably happen trying to play an out-of-region title online?",
'006-0332': 'Disconnected from the game server.', "006-0332": "Disconnected from the game server.",
'006-0502': 'Could not connect to the server.\n\n• Check the [network status page](http://support.nintendo.com/networkstatus)\n• Move closer to your wireless router\n• Verify DNS settings. If "Auto-Obtain" doesn\'t work, try Google\'s Public DNS (8.8.8.8, 8.8.4.4) and try again.', "006-0502": "Could not connect to the server.\n\n• Check the [network status page](http://support.nintendo.com/networkstatus)\n• Move closer to your wireless router\n• Verify DNS settings. If \"Auto-Obtain\" doesn't work, try Google's Public DNS (8.8.8.8, 8.8.4.4) and try again.",
'006-0612': 'Failed to join the session.', "006-0612": "Failed to join the session.",
'007-0200': 'Could not access SD card.', "007-0200": "Could not access SD card.",
'007-2001': 'Usually the result after region-changing the system. New 3DS cannot fix this issue right now.', "007-2001": "Usually the result after region-changing the system. New 3DS cannot fix this issue right now.",
'007-2100': 'The connection to the Nintendo eShop timed out.\nThis may be due to an ongoing server maintenance, check <https://support.nintendo.com/networkstatus> to make sure the servers are operating normally. You may also encounter this error if you have a weak internet connection.', "007-2100": "The connection to the Nintendo eShop timed out.\nThis may be due to an ongoing server maintenance, check <https://support.nintendo.com/networkstatus> to make sure the servers are operating normally. You may also encounter this error if you have a weak internet connection.",
'007-2404': 'An error occurred while attempting to connect to the Nintendo eShop.\nMake sure you are running the latest firmware, since this error will appear if you are trying to access the eShop on older versions.', "007-2404": "An error occurred while attempting to connect to the Nintendo eShop.\nMake sure you are running the latest firmware, since this error will appear if you are trying to access the eShop on older versions.",
'007-2720': 'SSL error?', "007-2720": "SSL error?",
'007-2916': 'HTTP error, server is probably down. Try again later?', "007-2916": "HTTP error, server is probably down. Try again later?",
'007-2920': 'This error is caused by installing a game or game update from an unofficial source, as it contains a bad ticket.\nThe only solution is to delete the unofficial game or update as well as its ticket\nin FBI, and install the game or update legitimately.', "007-2920": "This error is caused by installing a game or game update from an unofficial source, as it contains a bad ticket.\nThe only solution is to delete the unofficial game or update as well as its ticket\nin FBI, and install the game or update legitimately.",
'007-2913': 'HTTP error, server is probably down. Try again later?', "007-2913": "HTTP error, server is probably down. Try again later?",
'007-2923': 'The Nintendo Servers are currently down for maintenance. Please try again later.', "007-2923": "The Nintendo Servers are currently down for maintenance. Please try again later.",
'007-3102': 'Cannot find title on Nintendo eShop. Probably pulled.', "007-3102": "Cannot find title on Nintendo eShop. Probably pulled.",
'007-6054': 'Occurs when ticket database is full (8192 tickets).', "007-6054": "Occurs when ticket database is full (8192 tickets).",
'009-1000': 'System update required. (friends module?)', "009-1000": "System update required. (friends module?)",
'009-2916': 'NIM HTTP error, server is probably down. Try again later?', "009-2916": "NIM HTTP error, server is probably down. Try again later?",
'009-2913': 'NIM HTTP error, server is probably down. Try again later?', "009-2913": "NIM HTTP error, server is probably down. Try again later?",
'009-4079': 'Could not access SD card. General purpose error.', "009-4079": "Could not access SD card. General purpose error.",
'009-4998': '"Local content is newer."\nThe actual cause of this error is unknown.', "009-4998": '"Local content is newer."\nThe actual cause of this error is unknown.',
'009-6106': '"AM error in NIM."\nProbably a bad ticket.', "009-6106": '"AM error in NIM."\nProbably a bad ticket.',
'009-8401': 'Update data corrupted. Delete and re-install.', "009-8401": "Update data corrupted. Delete and re-install.",
'011-3021': 'Cannot find title on Nintendo eShop. Probably incorrect region, or never existed.', "011-3021": "Cannot find title on Nintendo eShop. Probably incorrect region, or never existed.",
'011-3136': 'Nintendo eShop is currently unavailable. Try again later.', "011-3136": "Nintendo eShop is currently unavailable. Try again later.",
'011-6901': 'System is banned by Nintendo, this error code description is oddly Japanese, generic error code. ', "011-6901": "System is banned by Nintendo, this error code description is oddly Japanese, generic error code. ",
'012-1511': 'Certificate warning.', "012-1511": "Certificate warning.",
'014-0016': 'Both systems have the same movable.sed key. Format the target and try system transfer again.', "014-0016": "Both systems have the same movable.sed key. Format the target and try system transfer again.",
'014-0062': 'Error during System Transfer. Move closer to the wireless router and keep trying.', "014-0062": "Error during System Transfer. Move closer to the wireless router and keep trying.",
'022-2452': 'Occurs when trying to use Nintendo eShop with UNITINFO patches enabled.', "022-2452": "Occurs when trying to use Nintendo eShop with UNITINFO patches enabled.",
'022-2501': 'Attempting to use a Nintendo Network ID on one system when it is linked on another. This can be the result of using System Transfer, then restoring the source system\'s NAND and attempting to use services that require a Nintendo Network ID.\n\nIn a System Transfer, all Nintendo Network ID accounts associated with the system are transferred over, whether they are currently linked or not.', "022-2501": "Attempting to use a Nintendo Network ID on one system when it is linked on another. This can be the result of using System Transfer, then restoring the source system's NAND and attempting to use services that require a Nintendo Network ID.\n\nIn a System Transfer, all Nintendo Network ID accounts associated with the system are transferred over, whether they are currently linked or not.",
'022-2511': 'System update required (what causes this? noticed while opening Miiverse, probably not friends module)', "022-2511": "System update required (what causes this? noticed while opening Miiverse, probably not friends module)",
'022-2613': 'Incorrect e-mail or password when trying to link an existing Nintendo Network ID. Make sure there are no typos, and the given e-mail is the correct one for the given ID.\nIf you forgot the password, reset it at <https://id.nintendo.net/account/forgotten-password>', "022-2613": "Incorrect e-mail or password when trying to link an existing Nintendo Network ID. Make sure there are no typos, and the given e-mail is the correct one for the given ID.\nIf you forgot the password, reset it at <https://id.nintendo.net/account/forgotten-password>",
'022-2631': 'Nintendo Network ID deleted, or not usable on the current system. If you used System Transfer, the Nintendo Network ID will only work on the target system.', "022-2631": "Nintendo Network ID deleted, or not usable on the current system. If you used System Transfer, the Nintendo Network ID will only work on the target system.",
'022-2633': 'Nintendo Network ID temporarily locked due to too many incorrect password attempts. Try again later.', "022-2633": "Nintendo Network ID temporarily locked due to too many incorrect password attempts. Try again later.",
'022-2634': 'Nintendo Network ID is not correctly linked on the system. This can be a result of formatting the SysNAND using System Settings to unlink it from the EmuNAND.\n\n<steps on how to fix>\n\nTinyFormat is recommended for unlinking in the future.', "022-2634": "Nintendo Network ID is not correctly linked on the system. This can be a result of formatting the SysNAND using System Settings to unlink it from the EmuNAND.\n\n<steps on how to fix>\n\nTinyFormat is recommended for unlinking in the future.",
'022-2812': 'System is permanently banned by Nintendo for illegally playing the Pokemon Sun & Moon ROM leak online before release. ', "022-2812": "System is permanently banned by Nintendo for illegally playing the Pokemon Sun & Moon ROM leak online before release. ",
'022-2815': 'System is banned by Nintendo from Miiverse access.', "022-2815": "System is banned by Nintendo from Miiverse access.",
'032-1820': 'Browser error that asks whether you want to go on to a potentially dangerous website. Can be bypassed by touching "yes".', "032-1820": 'Browser error that asks whether you want to go on to a potentially dangerous website. Can be bypassed by touching "yes".',
'090-0212': 'Game is permanently banned from Pokémon Global Link. This is most likely as a result of using altered or illegal save data.', "090-0212": "Game is permanently banned from Pokémon Global Link. This is most likely as a result of using altered or illegal save data.",
} }
wii_u_errors = { wii_u_errors = {
'102-2802': 'NNID is permanently banned by Nintendo. ', "102-2802": "NNID is permanently banned by Nintendo. ",
'102-2805': 'System is banned from accessing Nintendo eShop. ', "102-2805": "System is banned from accessing Nintendo eShop. ",
'102-2812': 'System + linked NNID and access to online services are permanently banned by Nintendo. ', "102-2812": "System + linked NNID and access to online services are permanently banned by Nintendo. ",
'102-2813': 'System is banned by Nintendo. ', "102-2813": "System is banned by Nintendo. ",
'102-2814': 'System is permanently banned from online multiplayer in a/multiple game(s) (preferably Splatoon). ', "102-2814": "System is permanently banned from online multiplayer in a/multiple game(s) (preferably Splatoon). ",
'102-2815': 'System is banned from accessing the Nintendo eShop. ', "102-2815": "System is banned from accessing the Nintendo eShop. ",
'102-2816': 'System is banned for a/multiple game(s) (preferably Splatoon) for an unknown duration, by attempting to use modified static.pack/+ game files online. ', "102-2816": "System is banned for a/multiple game(s) (preferably Splatoon) for an unknown duration, by attempting to use modified static.pack/+ game files online. ",
'106-0306': 'NNID is temporarily banned from a/multiple games (preferably Splatoon) online multiplayer. ', "106-0306": "NNID is temporarily banned from a/multiple games (preferably Splatoon) online multiplayer. ",
'106-0346': 'NNID is permanently banned from a/multiple games (preferably Splatoon) online multiplayer. ', "106-0346": "NNID is permanently banned from a/multiple games (preferably Splatoon) online multiplayer. ",
'115-1009': 'System is permanently banned from Miiverse.', "115-1009": "System is permanently banned from Miiverse.",
'121-0902': 'Permissions missing for the action you are trying to perfrom (Miiverse error).', "121-0902": "Permissions missing for the action you are trying to perfrom (Miiverse error).",
'150-1031': 'Disc could not be read. Either the disc is dirty, the lens is dirty, or the disc is unsupported (i.e. not a Wii or Wii U game).', "150-1031": "Disc could not be read. Either the disc is dirty, the lens is dirty, or the disc is unsupported (i.e. not a Wii or Wii U game).",
'160-0101': '"Generic error". Can happen when formatting a system with CBHC.', "160-0101": '"Generic error". Can happen when formatting a system with CBHC.',
'160-0102': 'Error in SLC/MLC or USB.', "160-0102": "Error in SLC/MLC or USB.",
'160-0103': '"The system memory is corrupted (MLC)."', "160-0103": '"The system memory is corrupted (MLC)."',
'160-0104': '"The system memory is corrupted (SLC)."', "160-0104": '"The system memory is corrupted (SLC)."',
'160-0105': 'USB storage corrupted?', "160-0105": "USB storage corrupted?",
'199-9999': 'Usually occurs when trying to run an unsigned title without signature patches, or something unknown(?) is corrupted.', "199-9999": "Usually occurs when trying to run an unsigned title without signature patches, or something unknown(?) is corrupted.",
} }
# 1K (+120) Lines PogChamp # 1K (+120) Lines PogChamp

View file

@ -1,11 +1,13 @@
import json import json
import time import time
userlog_event_types = {"warns": "Warn", userlog_event_types = {
"bans": "Ban", "warns": "Warn",
"kicks": "Kick", "bans": "Ban",
"mutes": "Mute", "kicks": "Kick",
"notes": "Note"} "mutes": "Mute",
"notes": "Note",
}
def get_userlog(): def get_userlog():
@ -22,27 +24,31 @@ def fill_userlog(userid, uname):
userlogs = get_userlog() userlogs = get_userlog()
uid = str(userid) uid = str(userid)
if uid not in userlogs: if uid not in userlogs:
userlogs[uid] = {"warns": [], userlogs[uid] = {
"mutes": [], "warns": [],
"kicks": [], "mutes": [],
"bans": [], "kicks": [],
"notes": [], "bans": [],
"watch": False, "notes": [],
"name": "n/a"} "watch": False,
"name": "n/a",
}
if uname: if uname:
userlogs[uid]["name"] = uname userlogs[uid]["name"] = uname
return userlogs, uid return userlogs, uid
def userlog(uid, issuer, reason, event_type, uname: str = ""): def userlog(uid, issuer, reason, event_type, uname: str = ""):
userlogs, uid = fill_userlog(uid, uname) userlogs, uid = fill_userlog(uid, uname)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
log_data = {"issuer_id": issuer.id, log_data = {
"issuer_name": f"{issuer}", "issuer_id": issuer.id,
"reason": reason, "issuer_name": f"{issuer}",
"timestamp": timestamp} "reason": reason,
"timestamp": timestamp,
}
if event_type not in userlogs[uid]: if event_type not in userlogs[uid]:
userlogs[uid][event_type] = [] userlogs[uid][event_type] = []
userlogs[uid][event_type].append(log_data) userlogs[uid][event_type].append(log_data)