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

View file

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

View file

@ -32,50 +32,51 @@ class Basic(Cog):
async def communitycount(self, ctx):
"""Prints the community member count of the server."""
community = ctx.guild.get_role(config.named_roles["community"])
await ctx.send(f"{ctx.guild.name} has "
f"{len(community.members)} community members!")
await ctx.send(
f"{ctx.guild.name} has " f"{len(community.members)} community members!"
)
@commands.guild_only()
@commands.command()
async def hackercount(self, ctx):
"""Prints the hacker member count of the server."""
h4x0r = ctx.guild.get_role(config.named_roles["hacker"])
await ctx.send(f"{ctx.guild.name} has "
f"{len(h4x0r.members)} people with hacker role!")
await ctx.send(
f"{ctx.guild.name} has " f"{len(h4x0r.members)} people with hacker role!"
)
@commands.guild_only()
@commands.command()
async def membercount(self, ctx):
"""Prints the member count of the server."""
await ctx.send(f"{ctx.guild.name} has "
f"{ctx.guild.member_count} members!")
await ctx.send(f"{ctx.guild.name} has " f"{ctx.guild.member_count} members!")
@commands.command(aliases=["robocopng", "robocop-ng"])
async def robocop(self, ctx):
"""Shows a quick embed with bot info."""
embed = discord.Embed(title="Robocop-NG",
url=config.source_url,
description=config.embed_desc)
embed = discord.Embed(
title="Robocop-NG", url=config.source_url, description=config.embed_desc
)
embed.set_thumbnail(url=self.bot.user.avatar_url)
await ctx.send(embed=embed)
@commands.command(aliases=['p'])
@commands.command(aliases=["p"])
async def ping(self, ctx):
"""Shows ping values to discord.
RTT = Round-trip time, time taken to send a message to discord
GW = Gateway Ping"""
before = time.monotonic()
tmp = await ctx.send('Calculating ping...')
tmp = await ctx.send("Calculating ping...")
after = time.monotonic()
rtt_ms = (after - before) * 1000
gw_ms = self.bot.latency * 1000
message_text = f":ping_pong:\n"\
f"rtt: `{rtt_ms:.1f}ms`\n"\
f"gw: `{gw_ms:.1f}ms`"
message_text = (
f":ping_pong:\n" f"rtt: `{rtt_ms:.1f}ms`\n" f"gw: `{gw_ms:.1f}ms`"
)
self.bot.log.info(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))
return res_timestamp
def get_relative_timestamp(self, time_from=None, time_to=None,
humanized=False, include_from=False,
include_to=False):
def get_relative_timestamp(
self,
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
# which is not what we want
if not time_from:
@ -43,17 +48,19 @@ class Common(Cog):
if humanized:
humanized_string = humanize.naturaltime(time_from - time_to)
if include_from and include_to:
str_with_from_and_to = f"{humanized_string} "\
f"({str(time_from).split('.')[0]} "\
f"- {str(time_to).split('.')[0]})"
str_with_from_and_to = (
f"{humanized_string} "
f"({str(time_from).split('.')[0]} "
f"- {str(time_to).split('.')[0]})"
)
return str_with_from_and_to
elif include_from:
str_with_from = f"{humanized_string} "\
f"({str(time_from).split('.')[0]})"
str_with_from = (
f"{humanized_string} " f"({str(time_from).split('.')[0]})"
)
return str_with_from
elif include_to:
str_with_to = f"{humanized_string} "\
f"({str(time_to).split('.')[0]})"
str_with_to = f"{humanized_string} " f"({str(time_to).split('.')[0]})"
return str_with_to
return humanized_string
else:
@ -61,8 +68,7 @@ class Common(Cog):
epoch_from = (time_from - epoch).total_seconds()
epoch_to = (time_to - epoch).total_seconds()
second_diff = epoch_to - epoch_from
result_string = str(datetime.timedelta(
seconds=second_diff)).split('.')[0]
result_string = str(datetime.timedelta(seconds=second_diff)).split(".")[0]
return result_string
async def aioget(self, url):
@ -73,11 +79,12 @@ class Common(Cog):
self.bot.log.info(f"Data from {url}: {text_data}")
return text_data
else:
self.bot.log.error(f"HTTP Error {data.status} "
"while getting {url}")
self.bot.log.error(f"HTTP Error {data.status} " "while getting {url}")
except:
self.bot.log.error(f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}")
self.bot.log.error(
f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}"
)
async def aiogetbytes(self, url):
try:
@ -87,11 +94,12 @@ class Common(Cog):
self.bot.log.debug(f"Data from {url}: {byte_data}")
return byte_data
else:
self.bot.log.error(f"HTTP Error {data.status} "
"while getting {url}")
self.bot.log.error(f"HTTP Error {data.status} " "while getting {url}")
except:
self.bot.log.error(f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}")
self.bot.log.error(
f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}"
)
async def aiojson(self, url):
try:
@ -99,18 +107,19 @@ class Common(Cog):
if data.status == 200:
text_data = await data.text()
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)
else:
self.bot.log.error(f"HTTP Error {data.status} "
"while getting {url}")
self.bot.log.error(f"HTTP Error {data.status} " "while getting {url}")
except:
self.bot.log.error(f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}")
self.bot.log.error(
f"Error while getting {url} "
f"on aiogetbytes: {traceback.format_exc()}"
)
def hex_to_int(self, color_hex: str):
"""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):
"""Escapes unfun stuff from messages"""
@ -130,10 +139,12 @@ class Common(Cog):
"""Slices a message into multiple messages"""
if len(text) > size * self.max_split_length:
haste_url = await self.haste(text)
return [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}>"]
return [
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}>"
]
reply_list = []
size_wo_fix = size - len(prefix) - len(suffix)
while len(text) > size_wo_fix:
@ -142,28 +153,28 @@ class Common(Cog):
reply_list.append(f"{prefix}{text}{suffix}")
return reply_list
async def haste(self, text, instance='https://mystb.in/'):
response = await self.bot.aiosession.post(f"{instance}documents",
data=text)
async def haste(self, text, instance="https://mystb.in/"):
response = await self.bot.aiosession.post(f"{instance}documents", data=text)
if response.status == 200:
result_json = await response.json()
return f"{instance}{result_json['key']}"
else:
return f"Error {response.status}: {response.text}"
async def async_call_shell(self, shell_command: str,
inc_stdout=True, inc_stderr=True):
async def async_call_shell(
self, shell_command: str, inc_stdout=True, inc_stderr=True
):
pipe = asyncio.subprocess.PIPE
proc = await asyncio.create_subprocess_shell(str(shell_command),
stdout=pipe,
stderr=pipe)
proc = await asyncio.create_subprocess_shell(
str(shell_command), stdout=pipe, stderr=pipe
)
if not (inc_stdout or inc_stderr):
return "??? you set both stdout and stderr to False????"
proc_result = await proc.communicate()
stdout_str = proc_result[0].decode('utf-8').strip()
stderr_str = proc_result[1].decode('utf-8').strip()
stdout_str = proc_result[0].decode("utf-8").strip()
stderr_str = proc_result[1].decode("utf-8").strip()
if inc_stdout and not inc_stderr:
return stdout_str
@ -171,8 +182,7 @@ class Common(Cog):
return stderr_str
if stdout_str and stderr_str:
return f"stdout:\n\n{stdout_str}\n\n"\
f"======\n\nstderr:\n\n{stderr_str}"
return f"stdout:\n\n{stdout_str}\n\n" f"======\n\nstderr:\n\n{stderr_str}"
elif stdout_str:
return f"stdout:\n\n{stdout_str}"
elif stderr_str:

View file

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

View file

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

View file

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

View file

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

View file

@ -25,64 +25,74 @@ class Links(Cog):
@commands.command(hidden=True, aliases=["xyproblem"])
async def xy(self, ctx):
"""Link to the "What is the XY problem?" post from SE"""
await ctx.send("<https://meta.stackexchange.com/q/66377/285481>\n\n"
"TL;DR: It's asking about your attempted solution "
"rather than your actual problem.\n"
"It's perfectly okay to want to learn about a "
"solution, but please be clear about your intentions "
"if you're not actually trying to solve a problem.")
await ctx.send(
"<https://meta.stackexchange.com/q/66377/285481>\n\n"
"TL;DR: It's asking about your attempted solution "
"rather than your actual problem.\n"
"It's perfectly okay to want to learn about a "
"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"])
async def guide(self, ctx):
"""Link to the guide(s)"""
await ctx.send("**Generic starter guides:**\n"
"Nintendo Homebrew's Guide: "
"<https://nh-server.github.io/switch-guide/>\n"
# "AtlasNX's Guide: "
# "<https://switch.homebrew.guide>\n"
# "Pegaswitch Guide: <https://switch.hacks.guide/> "
# "(outdated for anything but Pegaswitch/3.0.0)\n"
"\n**Specific guides:**\n"
"Manually Updating/Downgrading (with HOS): "
"<https://switch.homebrew.guide/usingcfw/manualupgrade>\n"
"Manually Repairing/Downgrading (without HOS): "
"<https://switch.homebrew.guide/usingcfw/manualchoiupgrade>\n"
"How to set up a Homebrew development environment: "
"<https://devkitpro.org/wiki/Getting_Started>\n"
"Getting full RAM in homebrew without NSPs: "
"as of Atmosphere 0.8.6, hold R while opening any game.\n"
"Check if a switch is vulnerable to RCM through serial: "
"<https://akdm.github.io/ssnc/checker/>")
await ctx.send(
"**Generic starter guides:**\n"
"Nintendo Homebrew's Guide: "
"<https://nh-server.github.io/switch-guide/>\n"
# "AtlasNX's Guide: "
# "<https://switch.homebrew.guide>\n"
# "Pegaswitch Guide: <https://switch.hacks.guide/> "
# "(outdated for anything but Pegaswitch/3.0.0)\n"
"\n**Specific guides:**\n"
"Manually Updating/Downgrading (with HOS): "
"<https://switch.homebrew.guide/usingcfw/manualupgrade>\n"
"Manually Repairing/Downgrading (without HOS): "
"<https://switch.homebrew.guide/usingcfw/manualchoiupgrade>\n"
"How to set up a Homebrew development environment: "
"<https://devkitpro.org/wiki/Getting_Started>\n"
"Getting full RAM in homebrew without NSPs: "
"as of Atmosphere 0.8.6, hold R while opening any game.\n"
"Check if a switch is vulnerable to RCM through serial: "
"<https://akdm.github.io/ssnc/checker/>"
)
@commands.command()
async def source(self, ctx):
"""Gives link to source code."""
await ctx.send(f"You can find my source at {config.source_url}. "
"Serious PRs and issues welcome!")
await ctx.send(
f"You can find my source at {config.source_url}. "
"Serious PRs and issues welcome!"
)
@commands.command()
async def rules(self, ctx, *, targetuser: discord.Member = None):
"""Post a link to the Rules"""
if not targetuser:
targetuser = ctx.author
await ctx.send(f"{targetuser.mention}: A link to the rules "
f"can be found here: {config.rules_url}")
await ctx.send(
f"{targetuser.mention}: A link to the rules "
f"can be found here: {config.rules_url}"
)
@commands.command()
async def community(self, ctx, *, targetuser: discord.Member = None):
"""Post a link to the community section of the rules"""
if not targetuser:
targetuser = ctx.author
await ctx.send(f"{targetuser.mention}: "
"https://reswitched.team/discord/#member-roles-breakdown"
"\n\n"
"Community role allows access to the set of channels "
"on the community category (#off-topic, "
"#homebrew-development, #switch-hacking-general etc)."
"\n\n"
"What you need to get the role is to be around, "
"be helpful and nice to people and "
"show an understanding of rules.")
await ctx.send(
f"{targetuser.mention}: "
"https://reswitched.team/discord/#member-roles-breakdown"
"\n\n"
"Community role allows access to the set of channels "
"on the community category (#off-topic, "
"#homebrew-development, #switch-hacking-general etc)."
"\n\n"
"What you need to get the role is to be around, "
"be helpful and nice to people and "
"show an understanding of rules."
)
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)
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):
return str(emoji)[0] == u"" or str(emoji)[0] == u""
return str(emoji)[0] == "" or str(emoji)[0] == ""
def is_recycle(self, emoji):
return str(emoji)[0] == u""
return str(emoji)[0] == ""
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):
return str(emoji)[0] == u"⤵️" or str(emoji)[0] == u""
return str(emoji)[0] == "⤵️" or str(emoji)[0] == ""
def is_reaction_valid(self, reaction):
allowed_reactions = [
u"",
u"📝",
u"",
u"",
u"",
u"⤴️",
u"",
u"",
u"⤵️",
"",
"📝",
"",
"",
"",
"⤴️",
"",
"",
"⤵️",
]
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 = []
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:
continue
@ -63,9 +63,11 @@ class Lists(Cog):
return reactions
def create_log_message(self, emoji, action, user, channel, reason = ""):
msg = f"{emoji} **{action}** \n"\
def create_log_message(self, emoji, action, user, channel, reason=""):
msg = (
f"{emoji} **{action}** \n"
f"from {self.bot.escape_message(user.name)} ({user.id}), in {channel.mention}"
)
if reason != "":
msg += f":\n`{reason}`"
@ -84,11 +86,11 @@ class Lists(Cog):
file_message = await files_channel.fetch_message(int(field.value))
await file_message.delete()
await message.edit(embed = None)
await message.edit(embed=None)
# Commands
@commands.command(aliases = ["list"])
@commands.command(aliases=["list"])
async def listitem(self, ctx, channel: discord.TextChannel, number: int):
"""Link to a specific list item."""
if number <= 0:
@ -100,20 +102,17 @@ class Lists(Cog):
return
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():
counter += 1
if counter == number:
embed = discord.Embed(
title = f"Item #{number} in #{channel.name}",
description = message.content,
url = message.jump_url
)
await ctx.send(
content = "",
embed = embed
title=f"Item #{number} in #{channel.name}",
description=message.content,
url=message.jump_url,
)
await ctx.send(content="", embed=embed)
return
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)
user = self.bot.get_user(payload.user_id)
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:
return
@ -155,8 +159,9 @@ class Lists(Cog):
# Remove all other reactions from user in this channel.
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
str(r.emoji) != str(reaction.emoji)):
if r.message.id != message.id or (
r.message.id == message.id and str(r.emoji) != str(reaction.emoji)
):
await r.remove(user)
# 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)
file = discord.File(
io.BytesIO(message.content.encode("utf-8")),
filename = f"{message.id}.txt")
file_message = await files_channel.send(file = file)
filename=f"{message.id}.txt",
)
file_message = await files_channel.send(file=file)
embed = discord.Embed(
title = "Click here to get the raw text to modify.",
url = f"{file_message.attachments[0].url}?")
embed.add_field(
name = "Message ID",
value = file_message.id,
inline = False)
await message.edit(embed = embed)
title="Click here to get the raw text to modify.",
url=f"{file_message.attachments[0].url}?",
)
embed.add_field(name="Message ID", value=file_message.id, inline=False)
await message.edit(embed=embed)
@Cog.listener()
async def on_raw_reaction_remove(self, payload):
@ -221,9 +225,16 @@ class Lists(Cog):
attachment_data = None
if len(message.attachments) != 0:
# Lists will only reupload the first image.
attachment = next((a for a in message.attachments if
a.filename.endswith(".png") or a.filename.endswith(".jpg") or
a.filename.endswith(".jpeg")), None)
attachment = next(
(
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:
attachment_filename = attachment.filename
attachment_data = await attachment.read()
@ -237,16 +248,18 @@ class Lists(Cog):
if len(reactions) != 1:
if attachment_filename is not None and attachment_data is not None:
file = discord.File(
io.BytesIO(attachment_data),
filename = attachment_filename)
await channel.send(content = content, file = file)
io.BytesIO(attachment_data), filename=attachment_filename
)
await channel.send(content=content, file=file)
else:
await channel.send(content)
for reaction in reactions:
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
targeted_reaction = reactions[0]
@ -255,47 +268,67 @@ class Lists(Cog):
if self.is_edit(targeted_reaction):
if config.list_files_channel != 0:
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 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):
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):
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten()
await channel.purge(limit = len(messages) + 1, bulk = True)
messages = await channel.history(
limit=None, after=targeted_message, oldest_first=True
).flatten()
await channel.purge(limit=len(messages) + 1, bulk=True)
await channel.send(targeted_message.content)
for message in messages:
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):
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten()
await channel.purge(limit = len(messages) + 1, bulk = True)
messages = await channel.history(
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(targeted_message.content)
for message in messages:
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):
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten()
await channel.purge(limit = len(messages) + 1, bulk = True)
messages = await channel.history(
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(content)
for message in messages:
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):

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -11,8 +11,9 @@ class ModUserlog(Cog):
def __init__(self, bot):
self.bot = bot
def get_userlog_embed_for_id(self, uid: str, name: str, own: bool = False,
event=""):
def get_userlog_embed_for_id(
self, uid: str, name: str, own: bool = False, event=""
):
own_note = " Good for you!" if own else ""
wanted_events = ["warns", "bans", "kicks", "mutes"]
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]:
event_name = userlog_event_types[event_type]
for idx, event in enumerate(userlog[uid][event_type]):
issuer = "" if own else f"Issuer: {event['issuer_name']} "\
f"({event['issuer_id']})\n"
embed.add_field(name=f"{event_name} {idx + 1}: "
f"{event['timestamp']}",
value=issuer + f"Reason: {event['reason']}",
inline=False)
issuer = (
""
if own
else f"Issuer: {event['issuer_name']} "
f"({event['issuer_id']})\n"
)
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]:
watch_state = "" if userlog[uid]["watch"] else "NOT "
@ -65,17 +71,17 @@ class ModUserlog(Cog):
if not event_count:
return f"<@{uid}> has no {event_type}!"
if idx > event_count:
return "Index is higher than "\
f"count ({event_count})!"
return "Index is higher than " f"count ({event_count})!"
if idx < 1:
return "Index is below 1!"
event = userlog[uid][event_type][idx - 1]
event_name = userlog_event_types[event_type]
embed = discord.Embed(color=discord.Color.dark_red(),
title=f"{event_name} {idx} on "
f"{event['timestamp']}",
description=f"Issuer: {event['issuer_name']}\n"
f"Reason: {event['reason']}")
embed = discord.Embed(
color=discord.Color.dark_red(),
title=f"{event_name} {idx} on " f"{event['timestamp']}",
description=f"Issuer: {event['issuer_name']}\n"
f"Reason: {event['reason']}",
)
del userlog[uid][event_type][idx - 1]
set_userlog(json.dumps(userlog))
return embed
@ -85,21 +91,18 @@ class ModUserlog(Cog):
@commands.command(aliases=["events"])
async def eventtypes(self, ctx):
"""Lists the available event types, staff only."""
event_list = [f"{et} ({userlog_event_types[et]})" for et in
userlog_event_types]
event_text = ("Available events:\n``` - " +
"\n - ".join(event_list) +
"```")
event_list = [f"{et} ({userlog_event_types[et]})" for et in userlog_event_types]
event_text = "Available events:\n``` - " + "\n - ".join(event_list) + "```"
await ctx.send(event_text)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(name="userlog",
aliases=["listwarns", "getuserlog", "listuserlog"])
@commands.command(
name="userlog", aliases=["listwarns", "getuserlog", "listuserlog"]
)
async def userlog_cmd(self, ctx, target: discord.Member, event=""):
"""Lists the userlog events for a user, staff only."""
embed = self.get_userlog_embed_for_id(str(target.id), str(target),
event=event)
embed = self.get_userlog_embed_for_id(str(target.id), str(target), event=event)
await ctx.send(embed=embed)
@commands.guild_only()
@ -107,16 +110,16 @@ class ModUserlog(Cog):
@commands.command(aliases=["listnotes", "usernotes"])
async def notes(self, ctx, target: discord.Member):
"""Lists the notes for a user, staff only."""
embed = self.get_userlog_embed_for_id(str(target.id), str(target),
event="notes")
embed = self.get_userlog_embed_for_id(
str(target.id), str(target), event="notes"
)
await ctx.send(embed=embed)
@commands.guild_only()
@commands.command(aliases=["mywarns"])
async def myuserlog(self, ctx):
"""Lists your userlog events (warns etc)."""
embed = self.get_userlog_embed_for_id(str(ctx.author.id),
str(ctx.author), True)
embed = self.get_userlog_embed_for_id(str(ctx.author.id), str(ctx.author), True)
await ctx.send(embed=embed)
@commands.guild_only()
@ -130,16 +133,17 @@ class ModUserlog(Cog):
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["clearwarns"])
async def clearevent(self, ctx, target: discord.Member,
event="warns"):
async def clearevent(self, ctx, target: discord.Member, event="warns"):
"""Clears all events of given type for a user, staff only."""
log_channel = self.bot.get_channel(config.modlog_channel)
msg = self.clear_event_from_id(str(target.id), event)
safe_name = await commands.clean_content().convert(ctx, str(target))
await ctx.send(msg)
msg = f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"\
f" all {event} events of {target.mention} | "\
f"{safe_name}"
msg = (
f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"
f" all {event} events of {target.mention} | "
f"{safe_name}"
)
await log_channel.send(msg)
@commands.guild_only()
@ -150,15 +154,16 @@ class ModUserlog(Cog):
log_channel = self.bot.get_channel(config.modlog_channel)
msg = self.clear_event_from_id(str(target), event)
await ctx.send(msg)
msg = f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"\
f" all {event} events of <@{target}> "
msg = (
f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"
f" all {event} events of <@{target}> "
)
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["delwarn"])
async def delevent(self, ctx, target: discord.Member, idx: int,
event="warns"):
async def delevent(self, ctx, target: discord.Member, idx: int, event="warns"):
"""Removes a specific event from a user, staff only."""
log_channel = self.bot.get_channel(config.modlog_channel)
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):
await ctx.send(f"{target.mention} has a {event_name} removed!")
safe_name = await commands.clean_content().convert(ctx, str(target))
msg = f"🗑 **Deleted {event_name}**: "\
f"{ctx.author.mention} removed "\
f"{event_name} {idx} from {target.mention} | {safe_name}"
msg = (
f"🗑 **Deleted {event_name}**: "
f"{ctx.author.mention} removed "
f"{event_name} {idx} from {target.mention} | {safe_name}"
)
await log_channel.send(msg, embed=del_event)
else:
await ctx.send(del_event)
@ -185,9 +192,11 @@ class ModUserlog(Cog):
# This is hell.
if isinstance(del_event, discord.Embed):
await ctx.send(f"<@{target}> has a {event_name} removed!")
msg = f"🗑 **Deleted {event_name}**: "\
f"{ctx.author.mention} removed "\
f"{event_name} {idx} from <@{target}> "
msg = (
f"🗑 **Deleted {event_name}**: "
f"{ctx.author.mention} removed "
f"{event_name} {idx} from <@{target}> "
)
await log_channel.send(msg, embed=del_event)
else:
await ctx.send(del_event)
@ -202,23 +211,26 @@ class ModUserlog(Cog):
role = "@ everyone"
event_types = ["warns", "bans", "kicks", "mutes", "notes"]
embed = self.get_userlog_embed_for_id(str(user.id), str(user),
event=event_types)
embed = self.get_userlog_embed_for_id(
str(user.id), str(user), event=event_types
)
user_name = await commands.clean_content().convert(ctx, user.name)
display_name = await commands.clean_content().convert(ctx, user.display_name)
await ctx.send(f"user = {user_name}\n"
f"id = {user.id}\n"
f"avatar = {user.avatar_url}\n"
f"bot = {user.bot}\n"
f"created_at = {user.created_at}\n"
f"display_name = {display_name}\n"
f"joined_at = {user.joined_at}\n"
f"activities = `{user.activities}`\n"
f"color = {user.colour}\n"
f"top_role = {role}\n",
embed=embed)
await ctx.send(
f"user = {user_name}\n"
f"id = {user.id}\n"
f"avatar = {user.avatar_url}\n"
f"bot = {user.bot}\n"
f"created_at = {user.created_at}\n"
f"display_name = {display_name}\n"
f"joined_at = {user.joined_at}\n"
f"activities = `{user.activities}`\n"
f"color = {user.colour}\n"
f"top_role = {role}\n",
embed=embed,
)
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_pin_channel
class Pin(Cog):
"""
Allow users to pin things
@ -17,9 +18,11 @@ class Pin(Cog):
self.bot = bot
def is_pinboard(self, msg):
return msg.author == self.bot.user and \
len(msg.embeds) > 0 and \
msg.embeds[0].title == "Pinboard"
return (
msg.author == self.bot.user
and len(msg.embeds) > 0
and msg.embeds[0].title == "Pinboard"
)
async def get_pinboard(self, gh, channel):
# Find pinboard pin
@ -32,21 +35,25 @@ class Pin(Cog):
return (id, data["files"]["pinboard.md"]["content"])
# Create pinboard pin if it does not exist
data = await gh.post("/gists", data={
"files": {
"pinboard.md": {
"content": "Old pins are available here:\n\n"
}
data = await gh.post(
"/gists",
data={
"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(
title="Pinboard",
description="Old pins are moved to the pinboard to make space for \
msg = await channel.send(
embed=Embed(
title="Pinboard",
description="Old pins are moved to the pinboard to make space for \
new ones. Check it out!",
url=data["html_url"]))
url=data["html_url"],
)
)
await msg.pin()
return (data["id"], data["files"]["pinboard.md"]["content"])
@ -57,18 +64,15 @@ class Pin(Cog):
return
async with aiohttp.ClientSession() as session:
gh = gidgethub.aiohttp.GitHubAPI(session, "RoboCop-NG",
oauth_token=config.github_oauth_token)
gh = gidgethub.aiohttp.GitHubAPI(
session, "RoboCop-NG", oauth_token=config.github_oauth_token
)
(id, content) = await self.get_pinboard(gh, channel)
content += "- " + data + "\n"
await gh.patch(f"/gists/{id}", data={
"files": {
"pinboard.md": {
"content": content
}
}
})
await gh.patch(
f"/gists/{id}", data={"files": {"pinboard.md": {"content": content}}}
)
@commands.command()
@commands.guild_only()
@ -136,7 +140,7 @@ class Pin(Cog):
break
# 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
await target_msg.pin()

View file

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

View file

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

View file

@ -23,18 +23,20 @@ class Verification(Cog):
await channel.purge(limit=limit)
await channel.send(config.welcome_header)
rules = ['**{}**. {}'.format(i, cleandoc(r)) for i, r in
enumerate(config.welcome_rules, 1)]
rules = [
"**{}**. {}".format(i, cleandoc(r))
for i, r in enumerate(config.welcome_rules, 1)
]
rule_choice = random.randint(2, len(rules))
hash_choice_str = self.hash_choice.upper()
if hash_choice_str == "BLAKE2B":
hash_choice_str += "-512"
elif hash_choice_str == "BLAKE2S":
hash_choice_str += "-256"
rules[rule_choice - 1] += \
'\n' + config.hidden_term_line.format(hash_choice_str)
msg = f"🗑 **Reset**: {author} cleared {limit} messages "\
f" in {channel.mention}"
rules[rule_choice - 1] += "\n" + config.hidden_term_line.format(hash_choice_str)
msg = (
f"🗑 **Reset**: {author} cleared {limit} messages " f" in {channel.mention}"
)
msg += f"\n💬 __Current challenge location__: under rule {rule_choice}"
log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(msg)
@ -64,13 +66,17 @@ class Verification(Cog):
async def do_resetalgo(self, channel, author, limit: int = 100):
# randomize hash_choice on reset
self.hash_choice = \
random.choice(tuple(hashlib.algorithms_guaranteed -
self.blacklisted_hashes -
{self.hash_choice}))
self.hash_choice = random.choice(
tuple(
hashlib.algorithms_guaranteed
- self.blacklisted_hashes
- {self.hash_choice}
)
)
msg = f"📘 **Reset Algorithm**: {author} reset "\
f"algorithm in {channel.mention}"
msg = (
f"📘 **Reset Algorithm**: {author} reset " f"algorithm in {channel.mention}"
)
msg += f"\n💬 __Current algorithm__: {self.hash_choice.upper()}"
log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(msg)
@ -82,8 +88,10 @@ class Verification(Cog):
async def reset(self, ctx, limit: int = 100, force: bool = False):
"""Wipes messages and pastes the welcome message again. Staff only."""
if ctx.message.channel.id != config.welcome_channel and not force:
await ctx.send(f"This command is limited to"
f" <#{config.welcome_channel}>, unless forced.")
await ctx.send(
f"This command is limited to"
f" <#{config.welcome_channel}>, unless forced."
)
return
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):
"""Resets the verification algorithm and does what reset does. Staff only."""
if ctx.message.channel.id != config.welcome_channel and not force:
await ctx.send(f"This command is limited to"
f" <#{config.welcome_channel}>, unless forced.")
await ctx.send(
f"This command is limited to"
f" <#{config.welcome_channel}>, unless forced."
)
return
await self.do_resetalgo(ctx.channel, ctx.author.mention, limit)
@ -113,13 +123,20 @@ class Verification(Cog):
mcl = message.content.lower()
# Reply to users that insult the bot
oof = ["bad", "broken", "buggy", "bugged",
"stupid", "dumb", "silly", "fuck", "heck", "h*ck"]
oof = [
"bad",
"broken",
"buggy",
"bugged",
"stupid",
"dumb",
"silly",
"fuck",
"heck",
"h*ck",
]
if "bot" in mcl and any(insult in mcl for insult in oof):
snark = random.choice(["bad human",
"no u",
"no u, rtfm",
"pebkac"])
snark = random.choice(["bad human", "no u", "no u, rtfm", "pebkac"])
return await chan.send(snark)
# 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
allowed_names = [f"@{full_name}", full_name, str(member.id)]
close_names = [f"@{member.name}", member.name, discrim,
f"#{discrim}"]
close_names = [f"@{member.name}", member.name, discrim, f"#{discrim}"]
# Now add the same things but with newlines at the end of them
allowed_names += [(an + '\n') for an in allowed_names]
close_names += [(cn + '\n') for cn in close_names]
allowed_names += [(an + '\r\n') for an in allowed_names]
close_names += [(cn + '\r\n') for cn in close_names]
allowed_names += [(an + "\n") for an in allowed_names]
close_names += [(cn + "\n") for cn in close_names]
allowed_names += [(an + "\r\n") for an in allowed_names]
close_names += [(cn + "\r\n") for cn in close_names]
# [ ͡° ͜ᔦ ͡°] 𝐖𝐞𝐥𝐜𝐨𝐦𝐞 𝐭𝐨 𝐌𝐚𝐜 𝐎𝐒 𝟗.
allowed_names += [(an + '\r') for an in allowed_names]
close_names += [(cn + '\r') for cn in close_names]
allowed_names += [(an + "\r") for an in allowed_names]
close_names += [(cn + "\r") for cn in close_names]
# Finally, hash the stuff so that we can access them later :)
hash_allow = [hashlib.new(self.hash_choice,
name.encode('utf-8')).hexdigest()
for name in allowed_names]
hash_allow = [
hashlib.new(self.hash_choice, name.encode("utf-8")).hexdigest()
for name in allowed_names
]
# I'm not even going to attempt to break those into lines jfc
if any(allow in mcl for allow in hash_allow):
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
wrong_hash_algos = hashlib.algorithms_guaranteed - \
{self.hash_choice} - self.blacklisted_hashes
wrong_hash_algos = (
hashlib.algorithms_guaranteed
- {self.hash_choice}
- self.blacklisted_hashes
)
for algo in wrong_hash_algos:
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)
await log_channel.send(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!")
await log_channel.send(
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."
rand_num = random.randint(1, 100)
if rand_num == 42:
@ -166,7 +202,7 @@ class Verification(Cog):
elif rand_num == 43:
no_text = "ugh, wrong, read the rules."
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}")
@Cog.listener()

View file

@ -11,31 +11,35 @@ source_url = "https://github.com/reswitched/robocop-ng"
rules_url = "https://reswitched.team/discord/#rules"
# The bot description to be used in .robocop embed
embed_desc = "Robocop-NG is developed by [Ave](https://github.com/aveao)"\
" and [tomGER](https://github.com/tumGER), and is a rewrite "\
"of Robocop.\nRobocop is based on Kurisu by 916253 and ihaveamac."
embed_desc = (
"Robocop-NG is developed by [Ave](https://github.com/aveao)"
" 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.
initial_cogs = ['cogs.common',
'cogs.admin',
'cogs.verification',
'cogs.mod',
'cogs.mod_note',
'cogs.mod_reacts',
'cogs.mod_userlog',
'cogs.mod_timed',
'cogs.mod_watch',
'cogs.basic',
'cogs.logs',
'cogs.err',
'cogs.lockdown',
'cogs.legacy',
'cogs.links',
'cogs.remind',
'cogs.robocronp',
'cogs.meme',
'cogs.invites']
initial_cogs = [
"cogs.common",
"cogs.admin",
"cogs.verification",
"cogs.mod",
"cogs.mod_note",
"cogs.mod_reacts",
"cogs.mod_userlog",
"cogs.mod_timed",
"cogs.mod_watch",
"cogs.basic",
"cogs.logs",
"cogs.err",
"cogs.lockdown",
"cogs.legacy",
"cogs.links",
"cogs.remind",
"cogs.robocronp",
"cogs.meme",
"cogs.invites",
]
# The following cogs are also available but aren't loaded by default:
# cogs.imagemanip - Adds a meme command called .cox.
@ -52,27 +56,27 @@ initial_cogs = ['cogs.common',
min_age = datetime.timedelta(minutes=15)
# The bot will only work in these guilds
guild_whitelist = [
269333940928512010 # ReSwitched discord
]
guild_whitelist = [269333940928512010] # ReSwitched discord
# Named roles to be used with .approve and .revoke
# Example: .approve User hacker
named_roles = {
"community": 420010997877833731,
"hacker": 364508795038072833,
"participant": 434353085926866946
"participant": 434353085926866946,
}
# The bot manager and staff roles
# Bot manager can run eval, exit and other destructive commands
# Staff can run administrative commands
bot_manager_role_id = 466447265863696394 # Bot management role in ReSwitched
staff_role_ids = [364647829248933888, # Team role in ReSwitched
360138431524765707, # Mod role in ReSwitched
466447265863696394, # Bot management role in ReSwitched
360138163156549632, # Admin role in ReSwitched
287289529986187266] # Wizard role in ReSwitched
staff_role_ids = [
364647829248933888, # Team role in ReSwitched
360138431524765707, # Mod role in ReSwitched
466447265863696394, # Bot management role in ReSwitched
360138163156549632, # Admin role in ReSwitched
287289529986187266,
] # Wizard role in ReSwitched
# Various log channels used to log bot and guild's activity
# 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
# access when we unmute on them
general_channels = [420029476634886144,
414949821003202562,
383368936466546698,
343244421044633602,
491316901692178432,
539212260350885908] # Channels everyone can access
community_channels = [269333940928512010,
438839875970662400,
404722395845361668,
435687501068501002,
286612533757083648] # Channels requiring community role
general_channels = [
420029476634886144,
414949821003202562,
383368936466546698,
343244421044633602,
491316901692178432,
539212260350885908,
] # Channels everyone can access
community_channels = [
269333940928512010,
438839875970662400,
404722395845361668,
435687501068501002,
286612533757083648,
] # Channels requiring community role
# Controls which roles are blocked during lockdown
lockdown_configs = {
# Used as a default value for channels without a config
"default": {
"channels": general_channels,
"roles": [named_roles["participant"]]
},
"default": {"channels": general_channels, "roles": [named_roles["participant"]]},
"community": {
"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
@ -135,24 +140,33 @@ list_files_channel = 0
list_channels = []
# All lower case, no spaces, nothing non-alphanumeric
suspect_words = ["sx", "tx", "reinx", # piracy-enabling cfws
"gomanx", # piracy-enabling cfws
"tinfoil", "dz", # title managers
"goldleaf", "lithium", # title managers
"cracked", # older term for pirated games
"xci", "nsz"] # "backup" format
suspect_words = [
"sx",
"tx",
"reinx", # piracy-enabling cfws
"gomanx", # piracy-enabling cfws
"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
# suspect_words (This is used to remove false positives)
suspect_ignored_words = ["excit",
"s/x",
"3dsx",
"psx",
"txt",
"s(x",
"txd",
"t=x",
"osx"]
suspect_ignored_words = [
"excit",
"s/x",
"3dsx",
"psx",
"txt",
"s(x",
"txd",
"t=x",
"osx",
]
# == Only if you want to use cogs.pin ==
# 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.
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
"""
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.
The use of derogatory slurs -- sexist, racist, homophobic, transphobic, or otherwise -- is unacceptable and may be grounds for an immediate ban.
""",
# 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.',
# 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.
To disable notifications for these pings, suppress them in "ReSwitched → Notification Settings".
""",
# 5
"""
Don't spam.
For excessively long text, use a service like <https://0bin.net/>.
""",
# 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.",
# 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
'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
"""
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.
This is a zero-tolerance, non-negotiable policy that is enforced strictly and swiftly, up to and including instant bans without warning.
""",
# 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
"""
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).
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.
""",
"""
#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):
if not ctx.guild:
return True
is_ot = (ctx.channel.name == "off-topic")
is_bot_cmds = (ctx.channel.name == "bot-cmds")
is_ot = ctx.channel.name == "off-topic"
is_bot_cmds = ctx.channel.name == "bot-cmds"
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):
if not ctx.guild:
return False
return any(r.id in config.staff_role_ids + config.allowed_pin_roles
for r in ctx.author.roles)
return any(
r.id in config.staff_role_ids + config.allowed_pin_roles
for r in ctx.author.roles
)
def check_if_pin_channel(ctx):

View file

@ -99,24 +99,18 @@ switch_modules = {
212: "GRC ",
216: "Migration ",
217: "Migration Idc Server ",
# Libnx
345: "libnx ",
346: "Homebrew ABI ",
347: "Homebrew Loader ",
348: "libnx Nvidia",
349: "libnx Binder",
# Support Errors
800: "General web-applet",
809: "WifiWebAuthApplet",
810: "Whitelisted-applet",
811: "ShopN",
# Custom Sysmodules
311: "SwitchPresence",
}
@ -150,11 +144,11 @@ switch_known_errcodes = {
0xFC01: "Reserved value ",
0xFE01: "Invalid hardware breakpoint ",
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 ",
0x10801: "Resource limit exceeded ",
0x20801: "Command buffer too small ",
0x40a01: "Invalid process ID.",
0x40A01: "Invalid process ID.",
0x40C01: "Invalid thread ID.",
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",
@ -248,7 +242,7 @@ switch_known_errcodes = {
0x3EA03: "Invalid handle ",
0x3EE03: "Invalid memory mirror ",
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 ",
0x1805: "Invalid StorageId ",
0xDC05: "Gamecard not inserted ",
@ -273,7 +267,7 @@ switch_known_errcodes = {
0xA09: "Invalid files. ",
0xE09: "Already registered. ",
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 ",
0x6A09: "Invalid NRR ",
0xA209: "Unaligned NRR address ",
@ -281,15 +275,15 @@ switch_known_errcodes = {
0xAA09: "Bad NRR address ",
0xAE09: "Bad initialization ",
0xC809: "Unknown ACI0 descriptor ",
0xCE09: "ACID/ACI0 don\'t match for descriptor KernelFlags ",
0xD009: "ACID/ACI0 don\'t match for descriptor SyscallMask ",
0xD409: "ACID/ACI0 don\'t match for descriptor MapIoOrNormalRange ",
0xD609: "ACID/ACI0 don\'t match for descriptor MapNormalPage ",
0xDE09: "ACID/ACI0 don\'t match for descriptor InterruptPair ",
0xE209: "ACID/ACI0 don\'t match for descriptor ApplicationType ",
0xE409: "ACID/ACI0 don\'t match for descriptor KernelReleaseVersion ",
0xE609: "ACID/ACI0 don\'t match for descriptor HandleTableSize ",
0xE809: "ACID/ACI0 don\'t match for descriptor DebugFlags ",
0xCE09: "ACID/ACI0 don't match for descriptor KernelFlags ",
0xD009: "ACID/ACI0 don't match for descriptor SyscallMask ",
0xD409: "ACID/ACI0 don't match for descriptor MapIoOrNormalRange ",
0xD609: "ACID/ACI0 don't match for descriptor MapNormalPage ",
0xDE09: "ACID/ACI0 don't match for descriptor InterruptPair ",
0xE209: "ACID/ACI0 don't match for descriptor ApplicationType ",
0xE409: "ACID/ACI0 don't match for descriptor KernelReleaseVersion ",
0xE609: "ACID/ACI0 don't match for descriptor HandleTableSize ",
0xE809: "ACID/ACI0 don't match for descriptor DebugFlags ",
0x1940A: "Invalid CMIF header size. ",
0x1A60A: "Invalid CMIF input header. ",
0x1A80A: "Invalid CMIF output header. ",
@ -300,7 +294,7 @@ switch_known_errcodes = {
0x20B: "Unsupported operation ",
0xCC0B: "Out of server session memory ",
0x11A0B: "Went past maximum during marshalling. ",
0x1900B: "Session doesn\'t support domains. ",
0x1900B: "Session doesn't support domains. ",
0x25A0B: "Remote process is dead. ",
0x3260B: "Unknown request type ",
0x3D60B: "IPC Query 1 failed. ",
@ -343,8 +337,8 @@ switch_known_errcodes = {
0x1BC69: "Empty settings item key ",
0x1E269: "Setting group 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_\-.]) ",
0x20C69: "Setting 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_\-.]) ",
0x4DA69: "Null language code buffer ",
0x4EE69: "Null network settings buffer ",
0x4F069: "Null network settings output count buffer ",
@ -469,7 +463,7 @@ switch_known_errcodes = {
0xD48C: "Invalid descriptor ",
0x1928C: "USB device not bound / interface already enabled ",
0x299: "Invalid audio device ",
0x499: "Operation couldn\'t complete successfully ",
0x499: "Operation couldn't complete successfully ",
0x699: "Invalid sample rate ",
0x899: "Buffer size too small ",
0x1099: "Too many buffers are still unreleased ",
@ -486,7 +480,7 @@ switch_known_errcodes = {
0xF0CD: "IR image data not available/ready. ",
0x35B: "Failed to init SM. ",
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. ",
0xB5B: "Invalid NRO magic. ",
0xD5B: "Invalid NRO segments. ",
@ -508,7 +502,7 @@ switch_known_errcodes = {
0x480: "Storage not available.",
0x1987E: "Development/debug-only behavior",
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",
0x27E: "Invalid argument",
0x7BC74: "Unimplemented functionality",
@ -565,15 +559,12 @@ switch_known_errcodes = {
0xC47A: "Invalid operation",
0x290: "Exited Abnormally ([[Applet_Manager_services#LibraryAppletExitReason|ExitReason]] == Abormal)",
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)",
0x58ACA: "Npad ID is out of range.",
0x1A8CD: "IR camera handle pointer is null.",
0x198CD: "IR camera invalid handle value.",
# FS Codes
0xD401: "Error: Passed buffer is not usable for fs library. ",
0x177A02: "Error: Specified value is out of range. ",
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(). ",
0x346402: "Error: Enough journal space is not left. ",
0x346A02: "Error: The open count of files and directories reached the limitation. ",
# Fatal
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",
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.",
0x6A8: "Userland PC address not aligned to 4 bytes ",
0x10A8: "Can occur when attempting to call an svc outside the whitelist ",
# Libnx Errors from libnx/result.h
# - Normal -
0x359: "LibnxError_BadReloc",
0x559: "LibnxError_OutOfMemory",
@ -641,7 +628,6 @@ switch_known_errcodes = {
0x5959: "LibnxError_NvinfoFailedToInitialize",
0x5B59: "LibnxError_NvbufFailedToInitialize",
0x5D59: "LibnxError_LibAppletBadExit",
# - Libnx Binder -
0x35D: "LibnxBinderError_Unknown",
0x55D: "LibnxBinderError_NoMemory",
@ -660,7 +646,6 @@ switch_known_errcodes = {
0x1F5D: "LibnxBinderError_TimedOut",
0x215D: "LibnxBinderError_UnknownTransaction",
0x235D: "LibnxBinderError_FdsNotAllowed",
# - LibNX Nvidia -
0x35C: "LibnxNvidiaError_Unknown",
0x55C: "LibnxNvidiaError_NotImplemented",
@ -681,91 +666,81 @@ switch_known_errcodes = {
0x235C: "LibnxNvidiaError_SharedMemoryTooSmall",
0x255C: "LibnxNvidiaError_FileOperationFailed",
0x275C: "LibnxNvidiaError_IoctlFailed",
# Non-SwitchBrew Error Codes - Should probably add them to SwitchBrew if you read this
0x7E12B: 'Eshop connection failed',
0x39D689: 'CDN Ban',
0x3E8E7C: 'Error in account login/creation',
0x3E8EA0: 'Failed connection test',
0x1F4E7C: '(normal) console ban',
0x27EE7C: '(potential) complete account ban', # This error is still super new, needs more informations
0x7E12B: "Eshop connection failed",
0x39D689: "CDN Ban",
0x3E8E7C: "Error in account login/creation",
0x3E8EA0: "Failed connection test",
0x1F4E7C: "(normal) console ban",
0x27EE7C: "(potential) complete account ban", # This error is still super new, needs more informations
0x36B72B: "Access token expired",
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.",
0x5089: "Failed to snprintf the AccountGetDynamicEtickets (personalized ticket) request JSON data.",
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?)",
0x8073: "Device unavailable",
0x10073: "App area not found",
0x11073: "Tag corrupted?",
0xc880: "thrown by AM when qlaunch is terminated",
0xc87c: "invalid user",
0xc7e: "mii already exists",
0xa7e: "full database",
0x87e: "mii not found",
0x115b: "[HBL] Stopped loading NROs",
0x48c69: "device_cert_ecc_b223 failed to load",
0xC880: "thrown by AM when qlaunch is terminated",
0xC87C: "invalid user",
0xC7E: "mii already exists",
0xA7E: "full database",
0x87E: "mii not found",
0x115B: "[HBL] Stopped loading NROs",
0x48C69: "device_cert_ecc_b223 failed to load",
0x138E02: "gamecard cmd buffer too small - must be 0x40 (or bigger)",
0x138E02: "gc out of bounds sector access",
0x13DC02: "gc sector start is out of range for partition 1",
0x13D802: "gc sector end out of range for partition 1",
0x13DA02: "gc sector wrong partition access",
# 0x3E8E89: 'Failed to access Firmware Updates - Often because of DNS!',
# ^ Also used by libcurl
# Atmosphere
0xCAFEF: "Atmosphere: Version Mismatch",
# Pegaswitch
0xa7200: "Fake-Error by Pegaswitch",
0xA7200: "Fake-Error by Pegaswitch",
# SwitchPresence
#Archived because the plugin got discontinued
#0x337: "Error_InitSocket",
#0x537: "Error_Listen",
#0x737: "Error_Accepting",
#0x937: "Error_ListAppFailed",
# Archived because the plugin got discontinued
# 0x337: "Error_InitSocket",
# 0x537: "Error_Listen",
# 0x737: "Error_Accepting",
# 0x937: "Error_ListAppFailed",
# 0xb37: "Error_InvalidMagic",
#0xd37: "Error_CmdIdNotConfirm",
#0xf37: "Error_CmdIdNotSendBuff",
#0x1137: "Error_RecData",
#0x1337: "Error_SendData",
#0x1537: "Error_InitNS",
#0x1737: "Error_InitACC",
#0x1937: "Error_GetControlData",
#0x1b37: "Error_InvalidControlSize",
#0x1d37: "Error_GetAciveUser",
#0x1f37: "Error_GetProfile",
#0x2137: "Error_ProfileGet",
#0x2337: "Error_InitPMDMNT",
#0x2537: "Error_GetAppPid",
#0x2737: "Error_GetProcessTid",
#0x2937: "Error_InitPMINFO",
#0x2b37: "Error_GetPidList",
#0x2d37: "Error_GetDebugProc",
#0x2f37: "Error_CloseHandle",
# 0xd37: "Error_CmdIdNotConfirm",
# 0xf37: "Error_CmdIdNotSendBuff",
# 0x1137: "Error_RecData",
# 0x1337: "Error_SendData",
# 0x1537: "Error_InitNS",
# 0x1737: "Error_InitACC",
# 0x1937: "Error_GetControlData",
# 0x1b37: "Error_InvalidControlSize",
# 0x1d37: "Error_GetAciveUser",
# 0x1f37: "Error_GetProfile",
# 0x2137: "Error_ProfileGet",
# 0x2337: "Error_InitPMDMNT",
# 0x2537: "Error_GetAppPid",
# 0x2737: "Error_GetProcessTid",
# 0x2937: "Error_InitPMINFO",
# 0x2b37: "Error_GetPidList",
# 0x2d37: "Error_GetDebugProc",
# 0x2f37: "Error_CloseHandle",
# Joke
0xDEADBEEF: "Congrats, you found some hexspeak \n \n https://www.youtube.com/watch?v=DLzxrzFCyOs",
# 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.",
}
switch_known_errcode_ranges = {
# NIM
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
2: [
[2000, 2499, "Error: Failed to access SD card."],
@ -786,12 +761,19 @@ switch_known_errcode_ranges = {
[6300, 6399, "Error: Unsupported operation."],
[6400, 6499, "Error: Permission denied."],
],
# NIFM Support Page Links
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 ;^)
@ -799,7 +781,6 @@ switch_known_errcode_ranges = {
switch_game_err = {
# Splatoon 2
"2-AAB6A-3400": "Splatoon 2: A kick from online due to exefs edits.",
# Youtube
"2-ARVHA-0000": "Youtube: Unknown Error",
}
@ -847,272 +828,271 @@ switch_support_page = {
}
dds_summaries = {
0: 'Success',
1: 'Nothing happened',
2: 'Would block',
3: 'Out of resource',
4: 'Not found',
5: 'Invalid state',
6: 'Not supported',
7: 'Invalid argument',
8: 'Wrong argument',
9: 'Canceled',
10: 'Status changed',
11: 'Internal',
63: 'Invalid result value'
0: "Success",
1: "Nothing happened",
2: "Would block",
3: "Out of resource",
4: "Not found",
5: "Invalid state",
6: "Not supported",
7: "Invalid argument",
8: "Wrong argument",
9: "Canceled",
10: "Status changed",
11: "Internal",
63: "Invalid result value",
}
dds_levels = {
0: "Success",
1: "Info",
25: "Status",
26: "Temporary",
27: "Permanent",
28: "Usage",
29: "Reinitialize",
30: "Reset",
31: "Fatal"
31: "Fatal",
}
dds_modules = {
0: 'Common',
1: 'Kernel',
2: 'Util',
3: 'File server',
4: 'Loader server',
5: 'TCB',
6: 'OS',
7: 'DBG',
8: 'DMNT',
9: 'PDN',
10: 'GSP',
11: 'I2C',
12: 'GPIO',
13: 'DD',
14: 'CODEC',
15: 'SPI',
16: 'PXI',
17: 'FS',
18: 'DI',
19: 'HID',
20: 'CAM',
21: 'PI',
22: 'PM',
23: 'PM_LOW',
24: 'FSI',
25: 'SRV',
26: 'NDM',
27: 'NWM',
28: 'SOC',
29: 'LDR',
30: 'ACC',
31: 'RomFS',
32: 'AM',
33: 'HIO',
34: 'Updater',
35: 'MIC',
36: 'FND',
37: 'MP',
38: 'MPWL',
39: 'AC',
40: 'HTTP',
41: 'DSP',
42: 'SND',
43: 'DLP',
44: 'HIO_LOW',
45: 'CSND',
46: 'SSL',
47: 'AM_LOW',
48: 'NEX',
49: 'Friends',
50: 'RDT',
51: 'Applet',
52: 'NIM',
53: 'PTM',
54: 'MIDI',
55: 'MC',
56: 'SWC',
57: 'FatFS',
58: 'NGC',
59: 'CARD',
60: 'CARDNOR',
61: 'SDMC',
62: 'BOSS',
63: 'DBM',
64: 'Config',
65: 'PS',
66: 'CEC',
67: 'IR',
68: 'UDS',
69: 'PL',
70: 'CUP',
71: 'Gyroscope',
72: 'MCU',
73: 'NS',
74: 'News',
75: 'RO',
76: 'GD',
77: 'Card SPI',
78: 'EC',
79: 'Web Browser',
80: 'Test',
81: 'ENC',
82: 'PIA',
83: 'ACT',
84: 'VCTL',
85: 'OLV',
86: 'NEIA',
87: 'NPNS',
90: 'AVD',
91: 'L2B',
92: 'MVD',
93: 'NFC',
94: 'UART',
95: 'SPM',
96: 'QTM',
97: 'NFP (amiibo)',
254: 'Application',
255: 'Invalid result value'
0: "Common",
1: "Kernel",
2: "Util",
3: "File server",
4: "Loader server",
5: "TCB",
6: "OS",
7: "DBG",
8: "DMNT",
9: "PDN",
10: "GSP",
11: "I2C",
12: "GPIO",
13: "DD",
14: "CODEC",
15: "SPI",
16: "PXI",
17: "FS",
18: "DI",
19: "HID",
20: "CAM",
21: "PI",
22: "PM",
23: "PM_LOW",
24: "FSI",
25: "SRV",
26: "NDM",
27: "NWM",
28: "SOC",
29: "LDR",
30: "ACC",
31: "RomFS",
32: "AM",
33: "HIO",
34: "Updater",
35: "MIC",
36: "FND",
37: "MP",
38: "MPWL",
39: "AC",
40: "HTTP",
41: "DSP",
42: "SND",
43: "DLP",
44: "HIO_LOW",
45: "CSND",
46: "SSL",
47: "AM_LOW",
48: "NEX",
49: "Friends",
50: "RDT",
51: "Applet",
52: "NIM",
53: "PTM",
54: "MIDI",
55: "MC",
56: "SWC",
57: "FatFS",
58: "NGC",
59: "CARD",
60: "CARDNOR",
61: "SDMC",
62: "BOSS",
63: "DBM",
64: "Config",
65: "PS",
66: "CEC",
67: "IR",
68: "UDS",
69: "PL",
70: "CUP",
71: "Gyroscope",
72: "MCU",
73: "NS",
74: "News",
75: "RO",
76: "GD",
77: "Card SPI",
78: "EC",
79: "Web Browser",
80: "Test",
81: "ENC",
82: "PIA",
83: "ACT",
84: "VCTL",
85: "OLV",
86: "NEIA",
87: "NPNS",
90: "AVD",
91: "L2B",
92: "MVD",
93: "NFC",
94: "UART",
95: "SPM",
96: "QTM",
97: "NFP (amiibo)",
254: "Application",
255: "Invalid result value",
}
dds_descriptions = {
0: 'Success',
2: 'Invalid memory permissions (kernel)',
4: 'Invalid ticket version (AM)',
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)',
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).',
10: 'Not enough memory (os)',
26: 'Session closed by remote (os)',
32: 'Empty CIA? (AM)',
37: 'Invalid NCCH? (AM)',
39: 'Invalid title version (AM)',
43: 'Database doesn\'t exist/failed to open (AM)',
44: 'Trying to uninstall system-app (AM)',
47: 'Invalid command header (OS)',
101: 'Archive not mounted/mount-point not found (fs)',
105: 'Request timed out (http)',
106: 'Invalid signature/CIA? (AM)',
120: 'Title/object not found? (fs)',
141: 'Gamecard not inserted? (fs)',
190: 'Object does already exist/failed to create object.',
230: 'Invalid open-flags / permissions? (fs)',
250: 'FAT operation denied (fs?)',
271: 'Invalid configuration (mvd).',
335: '(No permission? Seemed to appear when JKSM was being used without its XML.)',
391: 'NCCH hash-check failed? (fs)',
392: 'RSA/AES-MAC verification failed? (fs)',
393: 'Invalid database? (AM)',
395: 'RomFS/Savedata hash-check failed? (fs)',
630: 'Command not allowed / missing permissions? (fs)',
702: 'Invalid path? (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)',
1000: 'Invalid selection',
1001: 'Too large',
1002: 'Not authorized',
1003: 'Already done',
1004: 'Invalid size',
1005: 'Invalid enum value',
1006: 'Invalid combination',
1007: 'No data',
1008: 'Busy',
1009: 'Misaligned address',
1010: 'Misaligned size',
1011: 'Out of memory',
1012: 'Not implemented',
1013: 'Invalid address',
1014: 'Invalid pointer',
1015: 'Invalid handle',
1016: 'Not initialized',
1017: 'Already initialized',
1018: 'Not found',
1019: 'Cancel requested',
1020: 'Already exists',
1021: 'Out of range',
1022: 'Timeout',
1023: 'Invalid result value'
0: "Success",
2: "Invalid memory permissions (kernel)",
4: "Invalid ticket version (AM)",
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)",
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).",
10: "Not enough memory (os)",
26: "Session closed by remote (os)",
32: "Empty CIA? (AM)",
37: "Invalid NCCH? (AM)",
39: "Invalid title version (AM)",
43: "Database doesn't exist/failed to open (AM)",
44: "Trying to uninstall system-app (AM)",
47: "Invalid command header (OS)",
101: "Archive not mounted/mount-point not found (fs)",
105: "Request timed out (http)",
106: "Invalid signature/CIA? (AM)",
120: "Title/object not found? (fs)",
141: "Gamecard not inserted? (fs)",
190: "Object does already exist/failed to create object.",
230: "Invalid open-flags / permissions? (fs)",
250: "FAT operation denied (fs?)",
271: "Invalid configuration (mvd).",
335: "(No permission? Seemed to appear when JKSM was being used without its XML.)",
391: "NCCH hash-check failed? (fs)",
392: "RSA/AES-MAC verification failed? (fs)",
393: "Invalid database? (AM)",
395: "RomFS/Savedata hash-check failed? (fs)",
630: "Command not allowed / missing permissions? (fs)",
702: "Invalid path? (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)",
1000: "Invalid selection",
1001: "Too large",
1002: "Not authorized",
1003: "Already done",
1004: "Invalid size",
1005: "Invalid enum value",
1006: "Invalid combination",
1007: "No data",
1008: "Busy",
1009: "Misaligned address",
1010: "Misaligned size",
1011: "Out of memory",
1012: "Not implemented",
1013: "Invalid address",
1014: "Invalid pointer",
1015: "Invalid handle",
1016: "Not initialized",
1017: "Already initialized",
1018: "Not found",
1019: "Cancel requested",
1020: "Already exists",
1021: "Out of range",
1022: "Timeout",
1023: "Invalid result value",
}
# Nintendo Error Codes
dds_errcodes = {
# Nintendo 3DS
'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.',
'002-0102': 'System is permanently banned by Nintendo. ',
'002-0107': 'System is temporarily(?) banned by Nintendo. ',
'002-0119': 'System update required (outdated friends-module)',
'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-0123': 'System is generally banned by Nintendo. ',
'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.',
'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-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.',
'006-0102': 'Unexpected error. Could probably happen trying to play an out-of-region title online?',
'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-0612': 'Failed to join the session.',
'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-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-2720': 'SSL error?',
'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-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-3102': 'Cannot find title on Nintendo eShop. Probably pulled.',
'007-6054': 'Occurs when ticket database is full (8192 tickets).',
'009-1000': 'System update required. (friends module?)',
'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-4079': 'Could not access SD card. General purpose error.',
'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-8401': 'Update data corrupted. Delete and re-install.',
'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-6901': 'System is banned by Nintendo, this error code description is oddly Japanese, generic error code. ',
'012-1511': 'Certificate warning.',
'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.',
'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-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-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-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-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".',
'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.',
"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.",
"002-0102": "System is permanently banned by Nintendo. ",
"002-0107": "System is temporarily(?) banned by Nintendo. ",
"002-0119": "System update required (outdated friends-module)",
"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-0123": "System is generally banned by Nintendo. ",
"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.",
"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-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.",
"006-0102": "Unexpected error. Could probably happen trying to play an out-of-region title online?",
"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-0612": "Failed to join the session.",
"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-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-2720": "SSL error?",
"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-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-3102": "Cannot find title on Nintendo eShop. Probably pulled.",
"007-6054": "Occurs when ticket database is full (8192 tickets).",
"009-1000": "System update required. (friends module?)",
"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-4079": "Could not access SD card. General purpose error.",
"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-8401": "Update data corrupted. Delete and re-install.",
"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-6901": "System is banned by Nintendo, this error code description is oddly Japanese, generic error code. ",
"012-1511": "Certificate warning.",
"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.",
"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-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-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-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-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".',
"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 = {
'102-2802': 'NNID is permanently banned by Nintendo. ',
'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-2813': 'System is banned by Nintendo. ',
'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-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-0346': 'NNID is permanently banned from a/multiple games (preferably Splatoon) online multiplayer. ',
'115-1009': 'System is permanently banned from Miiverse.',
'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).',
'160-0101': '"Generic error". Can happen when formatting a system with CBHC.',
'160-0102': 'Error in SLC/MLC or USB.',
'160-0103': '"The system memory is corrupted (MLC)."',
'160-0104': '"The system memory is corrupted (SLC)."',
'160-0105': 'USB storage corrupted?',
'199-9999': 'Usually occurs when trying to run an unsigned title without signature patches, or something unknown(?) is corrupted.',
"102-2802": "NNID is permanently banned by Nintendo. ",
"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-2813": "System is banned by Nintendo. ",
"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-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-0346": "NNID is permanently banned from a/multiple games (preferably Splatoon) online multiplayer. ",
"115-1009": "System is permanently banned from Miiverse.",
"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).",
"160-0101": '"Generic error". Can happen when formatting a system with CBHC.',
"160-0102": "Error in SLC/MLC or USB.",
"160-0103": '"The system memory is corrupted (MLC)."',
"160-0104": '"The system memory is corrupted (SLC)."',
"160-0105": "USB storage corrupted?",
"199-9999": "Usually occurs when trying to run an unsigned title without signature patches, or something unknown(?) is corrupted.",
}
# 1K (+120) Lines PogChamp

View file

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