Browse Source

Strip Komet of all of it's useful stuff and leave all the cringe and memes.

main
Nichole Mattera 1 month ago
parent
commit
84b10dc7cf
30 changed files with 314 additions and 4492 deletions
  1. +75
    -136
      Robocop.py
  2. +19
    -74
      cogs/admin.py
  3. +31
    -26
      cogs/basic.py
  4. +53
    -42
      cogs/common.py
  5. +0
    -196
      cogs/err.py
  6. +0
    -43
      cogs/invites.py
  7. +29
    -45
      cogs/links.py
  8. +7
    -23
      cogs/lists.py
  9. +0
    -263
      cogs/lists_verification.py
  10. +0
    -90
      cogs/lockdown.py
  11. +0
    -353
      cogs/logs.py
  12. +76
    -50
      cogs/meme.py
  13. +0
    -551
      cogs/mod.py
  14. +0
    -143
      cogs/mod_mail.py
  15. +0
    -32
      cogs/mod_note.py
  16. +0
    -110
      cogs/mod_reacts.py
  17. +0
    -160
      cogs/mod_stats.py
  18. +0
    -137
      cogs/mod_timed.py
  19. +0
    -221
      cogs/mod_userlog.py
  20. +0
    -46
      cogs/mod_watch.py
  21. +0
    -157
      cogs/pin.py
  22. +0
    -68
      cogs/remind.py
  23. +0
    -153
      cogs/robocronp.py
  24. +1
    -0
      cogs/uwu.py
  25. +19
    -101
      config_template.py
  26. +4
    -6
      helpers/checks.py
  27. +0
    -1115
      helpers/errcodes.py
  28. +0
    -42
      helpers/restrictions.py
  29. +0
    -37
      helpers/robocronp.py
  30. +0
    -72
      helpers/userlogs.py

+ 75
- 136
Robocop.py View File

@@ -1,34 +1,20 @@
import os
import asyncio
import sys
import logging
import logging.handlers
import traceback
import aiohttp
import config

import discord
from discord.ext import commands

script_name = os.path.basename(__file__).split('.')[0]

log_file_name = f"{script_name}.log"

# Limit of discord (non-nitro) is 8MB (not MiB)
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)
stdout_handler = logging.StreamHandler(sys.stdout)

log_format = logging.Formatter(
'[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s')
file_handler.setFormatter(log_format)
"[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
)
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)


@@ -38,87 +24,49 @@ 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"]

initial_extensions = ['cogs.common',
'cogs.admin',
'cogs.mod',
'cogs.mod_mail',
'cogs.mod_note',
'cogs.mod_reacts',
'cogs.mod_stats',
'cogs.mod_userlog',
'cogs.mod_timed',
'cogs.mod_watch',
'cogs.basic',
'cogs.logs',
'cogs.lockdown',
'cogs.links',
'cogs.lists',
'cogs.lists_verification',
'cogs.remind',
'cogs.robocronp',
'cogs.meme',
'cogs.uwu']

bot = commands.Bot(command_prefix=get_prefix,
description=config.bot_description, pm_help=True)
initial_extensions = [
"cogs.common",
"cogs.admin",
"cogs.basic",
"cogs.links",
"cogs.lists",
"cogs.meme",
"cogs.uwu",
]

bot = commands.Bot(
command_prefix=get_prefix, description=config.bot_description, pm_help=True
)

bot.log = log
bot.loop = asyncio.get_event_loop()
bot.config = config
bot.script_name = script_name
bot.wanted_jsons = wanted_jsons

if __name__ == '__main__':
if __name__ == "__main__":
for extension in initial_extensions:
try:
bot.load_extension(extension)
except Exception as e:
log.error(f'Failed to load extension {extension}.')
log.error(f"Failed to load extension {extension}.")
log.error(traceback.print_exc())


@bot.event
async def on_ready():
aioh = {"User-Agent": f"{script_name}/1.0'"}
bot.aiosession = aiohttp.ClientSession(headers=aioh)
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!"

data_files = [discord.File(fpath) for fpath in wanted_jsons]
await bot.botlog_channel.send(msg, files=data_files)
msg = f"{bot.user.name} has started! "
await bot.botlog_channel.send(msg)

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}\" "
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})"
else:
log_text += f"on DMs ({ctx.channel.id})"
log.info(log_text)


@bot.event
async def on_error(event_method, *args, **kwargs):
log.error(f"Error on {event_method}: {sys.exc_info()}")
@@ -128,9 +76,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)

@@ -141,71 +91,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}"\
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}"
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}")


@bot.event
async def on_message(message):
if message.author.bot:
return

if (message.guild) and (message.guild.id not in config.guild_whitelist):
return

# 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):
return

ctx = await bot.get_context(message)
await bot.invoke(ctx)

if not os.path.exists("data"):
os.makedirs("data")
return await ctx.send(
f"{ctx.author.mention}: You gave incomplete " f"arguments. {help_text}"
)

for wanted_json in wanted_jsons:
if not os.path.exists(wanted_json):
with open(wanted_json, "w") as f:
f.write("{}")

bot.run(config.token, bot=True, reconnect=True)

+ 19
- 74
cogs/admin.py View File

@@ -2,22 +2,18 @@ import discord
from discord.ext import commands
from discord.ext.commands import Cog
import traceback
import inspect
import re
import config
from helpers.checks import check_if_bot_manager
from helpers.userlogs import set_userlog


class Admin(Cog):
def __init__(self, bot):
self.bot = bot
self.last_eval_result = None
self.previous_eval_code = None

@commands.guild_only()
@commands.check(check_if_bot_manager)
@commands.command(name='exit', aliases=["quit", "bye", "stop", "kill", "restart", "die"])
@commands.command(
name="exit", aliases=["quit", "bye", "stop", "kill", "restart", "die"]
)
async def _exit(self, ctx):
"""Shuts down the bot, bot manager only."""
await ctx.send(":wave: Goodbye!")
@@ -25,53 +21,15 @@ class Admin(Cog):

@commands.guild_only()
@commands.check(check_if_bot_manager)
@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"))

@commands.guild_only()
@commands.check(check_if_bot_manager)
@commands.command()
async def fetchdata(self, ctx):
"""Returns data files"""
data_files = [discord.File(fpath) for fpath in self.bot.wanted_jsons]
await ctx.send("Here you go:", files=data_files)

@commands.guild_only()
@commands.check(check_if_bot_manager)
@commands.command(name='eval')
@commands.command(name="eval")
async def _eval(self, ctx):
await ctx.send("Fuck off. This doesn't belong in production code!")

async def cog_load_actions(self, cog_name):
if cog_name == "verification":
verif_channel = self.bot.get_channel(config.welcome_channel)
await self.bot.do_resetalgo(verif_channel, "cog load")

@commands.guild_only()
@commands.check(check_if_bot_manager)
@commands.command()
async def pull(self, ctx, auto=False):
"""Does a git pull, bot manager only."""
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)
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.')
await self.cog_load_actions(cog)
except:
await ctx.send(f':x: Cog reloading failed, traceback: '
f'```\n{traceback.format_exc()}\n```')
return
await ctx.send("Fuck off. This doesn't belong in production code! Bother Nichole instead.")

@commands.guild_only()
@commands.check(check_if_bot_manager)
@@ -80,13 +38,14 @@ class Admin(Cog):
"""Loads a cog, bot manager only."""
try:
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)
@@ -94,8 +53,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()
@@ -111,28 +70,14 @@ 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.')

@commands.guild_only()
@commands.check(check_if_bot_manager)
@commands.command()
async def restoreuserlog(self, ctx, message_id: int):
"""Restores the user log from a saved backup."""
message = await self.bot.botlog_channel.fetch_message(message_id)
if message.author.id != self.bot.user.id or len(message.attachments) == 0:
await ctx.send("Incorrect Message ID.")
self.bot.log.info(f"Reloaded ext {ext}")
await ctx.send(f":white_check_mark: `{ext}` successfully reloaded.")

for attachment in message.attachments:
if attachment.filename == "userlog.json":
logs = (await attachment.read()).decode("utf-8")
set_userlog(logs)
return
await ctx.send("Incorrect Message ID.")

def setup(bot):
bot.add_cog(Admin(bot))

+ 31
- 26
cogs/basic.py View File

@@ -5,6 +5,7 @@ from discord.ext import commands
from discord.ext.commands import Cog
from helpers.checks import check_if_verified


class Basic(Cog):
def __init__(self, bot):
self.bot = bot
@@ -17,64 +18,68 @@ class Basic(Cog):
await ctx.send(f"Hello {ctx.author.mention}!")

@commands.check(check_if_verified)
@commands.command(aliases=['aboutkosmos'])
@commands.command(aliases=["aboutkosmos"])
async def about(self, ctx):
"""Shows what kosmos is and what it includes"""
await ctx.send("Kosmos is a CFW bundle that comes with Atmosphere, Hekate, and some homebrew. You can see all the homebrew that is included here: https://github.com/AtlasNX/Kosmos#featuring")
await ctx.send(
"Kosmos is a CFW bundle that comes with Atmosphere, Hekate, and some homebrew. You can see all the homebrew that is included here: https://github.com/AtlasNX/Kosmos#featuring"
)

@commands.check(check_if_verified)
@commands.command(aliases=["fat32"])
async def exfat(self, ctx):
"""Displays a helpful message on why not to use exFAT"""
embed = discord.Embed(title="GUIFormat",
url="http://www.ridgecrop.demon.co.uk/guiformat.exe",
description="A useful tool for formatting SD cards over 32GB as FAT32 on Windows.")
message_text=("The exFAT drivers built into the Switch has been known "
"to corrupt SD cards and homebrew only makes this worse. "
"Backup everything on your SD card as soon as possible "
"and format it to FAT32. On Windows, if your SD card is "
"over 32GB then it will not let you select FAT32 from "
"the built-in format tool, however you can use a tool "
"like GUIFormat to format it.")
await ctx.send(content=message_text,
embed=embed)
embed = discord.Embed(
title="GUIFormat",
url="http://www.ridgecrop.demon.co.uk/guiformat.exe",
description="A useful tool for formatting SD cards over 32GB as FAT32 on Windows.",
)
message_text = (
"The exFAT drivers built into the Switch has been known "
"to corrupt SD cards and homebrew only makes this worse. "
"Backup everything on your SD card as soon as possible "
"and format it to FAT32. On Windows, if your SD card is "
"over 32GB then it will not let you select FAT32 from "
"the built-in format tool, however you can use a tool "
"like GUIFormat to format it."
)
await ctx.send(content=message_text, embed=embed)

@commands.guild_only()
@commands.check(check_if_verified)
@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.check(check_if_verified)
@commands.command(aliases=["robocopng", "robocop-ng", "komet", "komet-cl"])
async def robocop(self, ctx):
"""Shows a quick embed with bot info."""
embed = discord.Embed(title="Komet",
url=config.source_url,
description=config.embed_desc)
embed = discord.Embed(
title="Komet", url=config.source_url, description=config.embed_desc
)

embed.set_thumbnail(url=self.bot.user.avatar_url)

await ctx.send(embed=embed)

@commands.check(check_if_verified)
@commands.command(aliases=['p', "ddos"])
@commands.command(aliases=["p", "ddos"])
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)



+ 53
- 42
cogs/common.py View File

@@ -7,6 +7,7 @@ import math
import parsedatetime
from discord.ext.commands import Cog


class Common(Cog):
def __init__(self, bot):
self.bot = bot
@@ -30,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:
@@ -42,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:
@@ -60,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):
@@ -72,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:
@@ -86,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:
@@ -98,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"""
@@ -129,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:
@@ -141,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
@@ -170,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:


+ 0
- 196
cogs/err.py View File

@@ -1,196 +0,0 @@
import re
import discord

from discord.ext import commands
from discord.ext.commands import Cog
from helpers.checks import check_if_verified
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 should report relevant details to "\
"<@141532589725974528> (tomGER#7462) "\
"so it can be added to the bot."
self.rickroll = "https://www.youtube.com/watch?v=4uj896lr3-E"

@commands.check(check_if_verified)
@commands.command(aliases=["3dserr", "3err", "dserr"])
async def dderr(self, ctx, err: str):
"""Searches for 3DS error codes!
Usage: .ddserr/.3err/.dserr/.3dserr <Error Code>"""
if self.dds_re.match(err): # 3DS - dds -> Drei DS -> Three DS
if err in dds_errcodes:
err_description = dds_errcodes[err]
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.set_footer(text="Console: 3DS")

# Send message, crazy
await ctx.send(embed=embed)

# These are not similar to the other errors apparently ... ?
elif err.startswith("0x"):
derr = err[2:]
derr = derr.strip()
rc = int(derr, 16)
desc = rc & 0x3FF
mod = (rc >> 10) & 0xFF
summ = (rc >> 21) & 0x3F
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="Summary", value=dds_summaries.get(summ, summ))
embed.add_field(name="Level", value=dds_levels.get(level, level))
embed.set_footer(text="Console: 3DS")

await ctx.send(embed=embed)
return
else:
await ctx.send("Unknown Format - This is either "
"no error code or you made some mistake!")

@commands.check(check_if_verified)
@commands.command(aliases=["wiiuserr", "uerr", "wuerr", "mochaerr"])
async def wiiuerr(self, ctx, err: str):
"""Searches for Wii U error codes!
Usage: .wiiuserr/.uerr/.wuerr/.mochaerr <Error Code>"""
if self.wiiu_re.match(err): # Wii U
module = err[2:3] # Is that even true, idk just guessing
desc = err[5:8]
if err in wii_u_errors:
err_description = wii_u_errors[err]
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.set_footer(text="Console: Wii U")
embed.add_field(name="Module", value=module, inline=True)
embed.add_field(name="Description", value=desc, inline=True)

# 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!")

@commands.check(check_if_verified)
@commands.command(aliases=["nxerr", "serr"])
async def err(self, ctx, err: str):
"""Searches for Switch error codes!
Usage: .serr/.nxerr/.err <Error Code>"""

if self.switch_re.match(err) or err.startswith("0x"): # Switch

if err.startswith("0x"):
err = err[2:]
errcode = int(err, 16)
module = errcode & 0x1FF
desc = (errcode >> 9) & 0x3FFF
else:
module = int(err[0:4]) - 2000
desc = int(err[5:9])
errcode = (desc << 9) + module

str_errcode = f'{(module + 2000):04}-{desc:04}'

# Searching for Modules in list
if module in switch_modules:
err_module = switch_modules[module]
else:
err_module = "Unknown"

# Set initial value unconditionally
err_description = self.no_err_desc

# Searching for error codes related to the Switch
# (doesn't include special cases)
if errcode in switch_known_errcodes:
err_description = switch_known_errcodes[errcode]
elif errcode in switch_support_page:
err_description = switch_support_page[errcode]
elif module in switch_known_errcode_ranges:
for errcode_range in switch_known_errcode_ranges[module]:
if desc >= errcode_range[0] and desc <= errcode_range[1]:
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.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
# its required to break their format lol
elif err in switch_game_err:
game, desc = switch_game_err[err].split(":")

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!")

@commands.check(check_if_verified)
@commands.command(aliases=["e2h"])
async def err2hex(self, ctx, err: str):
"""Converts Nintendo Switch errors to hex
Usage: .err2hex <Error Code>"""
if self.switch_re.match(err):
module = int(err[0:4]) - 2000
desc = int(err[5:9])
errcode = (desc << 9) + module
await ctx.send(hex(errcode))
else:
await ctx.send("This doesn't follow the typical"
" Nintendo Switch 2XXX-XXXX format!")

@commands.check(check_if_verified)
@commands.command(aliases=["h2e"])
async def hex2err(self, ctx, err: str):
"""Converts Nintendo Switch errors to hex
Usage: .hex2err <Hex>"""
if err.startswith("0x"):
err = err[2:]
err = int(err, 16)
module = err & 0x1FF
desc = (err >> 9) & 0x3FFF
errcode = f'{(module + 2000):04}-{desc:04}'
await ctx.send(errcode)
else:
await ctx.send("This doesn't look like typical hex!")


def setup(bot):
bot.add_cog(Err(bot))

+ 0
- 43
cogs/invites.py View File

@@ -1,43 +0,0 @@
from discord.ext import commands
from discord.ext.commands import Cog
from helpers.checks import check_if_collaborator
import config
import json

class Invites(Cog):
def __init__(self, bot):
self.bot = bot

@commands.command()
@commands.guild_only()
@commands.check(check_if_collaborator)
async def invite(self, ctx):
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)

with open("data/invites.json", "r") as f:
invites = json.load(f)

invites[invite.id] = {
"uses": 0,
"url": invite.url,
"max_uses": 1,
"code": invite.code
}

with open("data/invites.json", "w") as f:
f.write(json.dumps(invites))

await ctx.message.add_reaction("🆗")
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.")


def setup(bot):
bot.add_cog(Invites(bot))

+ 29
- 45
cogs/links.py View File

@@ -4,6 +4,7 @@ from discord.ext import commands
from discord.ext.commands import Cog
from helpers.checks import check_if_verified


class Links(Cog):
"""
Commands for easily linking to projects.
@@ -28,65 +29,45 @@ class Links(Cog):
@commands.command(hidden=True, aliases=["bootloader"])
async def hekate(self, ctx):
"""Link to the Hekate repo"""
await ctx.send("https://github.com/CTCaer/hekate")
await ctx.send("https://github.com/CTCaer/hekate")

@commands.check(check_if_verified)
@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.check(check_if_verified)
@commands.command(hidden=True, aliases=["guides", "link"])
async def guide(self, ctx):
"""Link to the guide(s)"""

message_text=("**Generic starter guides:**\n"
"AtlasNX's Guide: "
"<https://switch.homebrew.guide>\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 get started developing Homebrew: "
"<https://switch.homebrew.guide/homebrew_dev/introduction>\n"
"\n")

try:
support_faq_channel = self.bot.get_channel(config.support_faq_channel)
if support_faq_channel is None:
message_text += "Check out #support-faq for additional help."
else:
message_text += f"Check out {support_faq_channel.mention} for additional help."
except AttributeError:
message_text += "Check out #support-faq for additional help."
await ctx.send(message_text)

@commands.check(check_if_verified)
@commands.command(hidden=True, aliases=["patron"])
async def patreon(self, ctx):
"""Link to the patreon"""
await ctx.send("https://patreon.teamatlasnx.com")

@commands.check(check_if_verified)
@commands.command(hidden=True, aliases=["coffee"])
async def kofi(self, ctx):
"""Link to Ko-fi"""
await ctx.send("https://kofi.teamatlasnx.com")
await ctx.send(
"**Generic starter guides:**\n"
"SDSetup Guide: "
"<https://switch.homebrew.guide>\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 get started developing Homebrew: "
"<https://switch.homebrew.guide/homebrew_dev/introduction>"
)

@commands.check(check_if_verified)
@commands.command(hidden=True, aliases=["sdfiles"])
async def kosmos(self, ctx):
"""Link to the latest Kosmos release"""
await ctx.send("https://github.com/AtlasNX/Kosmos/releases/latest")
await ctx.send("https://github.com/Team-Neptune/DeepSea/releases/latest")

@commands.check(check_if_verified)
@commands.command(hidden=True, aliases=["sd"])
@@ -98,8 +79,11 @@ class Links(Cog):
@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!"
)


def setup(bot):
bot.add_cog(Links(bot))

+ 7
- 23
cogs/lists.py View File

@@ -209,22 +209,6 @@ class Lists(Cog):
mod_cog, ctx=ctx, target=target, reason=f"Rule {number} - {reason}"
)

@commands.guild_only()
@commands.check(check_if_verified)
@commands.command(aliases=["faq"])
async def support(self, ctx, number: int):
"""Link to a specific list item in #support-faq"""
channel = ctx.guild.get_channel(config.support_faq_channel)
await self.link_list_item(ctx, channel, number)

@commands.guild_only()
@commands.check(check_if_verified)
@commands.command(aliases=["es", "fs", "acid", "piracy"])
async def patches(self, ctx):
"""Link to the list item in #support-faq about patches"""
channel = ctx.guild.get_channel(config.support_faq_channel)
await self.link_list_item(ctx, channel, 1)

# Listeners

@Cog.listener()
@@ -324,7 +308,7 @@ class Lists(Cog):
await message.delete()
return

log_channel = self.bot.get_channel(config.log_channel)
botlog_channel = self.bot.get_channel(config.botlog_channel)
channel = message.channel
content = message.content
user = message.author
@@ -363,7 +347,7 @@ class Lists(Cog):
for reaction in reactions:
await reaction.remove(user)

await log_channel.send(
await botlog_channel.send(
self.create_log_message("💬", "List item added:", user, channel)
)
return
@@ -377,14 +361,14 @@ class Lists(Cog):
await targeted_message.edit(content=content)
await targeted_reaction.remove(user)

await log_channel.send(
await botlog_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(
await botlog_channel.send(
self.create_log_message(
"❌", "List item deleted:", user, channel, content
)
@@ -403,7 +387,7 @@ class Lists(Cog):
for message in messages:
await self.send_cached_message(channel, message)

await log_channel.send(
await botlog_channel.send(
self.create_log_message(
"♻", "List item recycled:", user, channel, content
)
@@ -430,7 +414,7 @@ class Lists(Cog):
for message in messages:
await self.send_cached_message(channel, message)

await log_channel.send(
await botlog_channel.send(
self.create_log_message("💬", "List item added:", user, channel)
)

@@ -457,7 +441,7 @@ class Lists(Cog):
for message in messages:
await self.send_cached_message(channel, message)

await log_channel.send(
await botlog_channel.send(
self.create_log_message("💬", "List item added:", user, channel)
)



+ 0
- 263
cogs/lists_verification.py View File

@@ -1,263 +0,0 @@
import asyncio
import config
import discord
from discord.ext import commands
from discord.ext.commands import Cog
from helpers.checks import check_if_staff
import io
import os
import random
import re


class ListsVerification(Cog):
def __init__(self, bot):
self.bot = bot
self.verification = ""
if os.path.exists("data/verification.txt"):
with open("data/verification.txt", "r") as f:
self.verification = f.read()

bot.loop.create_task(self.daily())

def generate_verification_phrase(self):
random_words = [
"colony",
"carriage",
"be",
"employee",
"empirical",
"flourish",
"moral",
"troop",
"waterfall",
"reduction",
"fraction",
"goalkeeper",
"conscious",
"acceptable",
"advertising",
"visual",
"spin",
"margin",
"greeting",
"continuation",
"sandwich",
"upset",
"stake",
"safe",
"rally",
"reservoir",
"effort",
"integration",
"extent",
"expression",
"echo",
"prove",
"precedent",
"inhibition",
"expect",
"theft",
"distinct",
"part",
"revolution",
"player",
"fragrant",
"waste",
"value",
"profession",
"quote",
"room",
"master",
"utter",
"aloof",
"quantity",
]

self.verification = (
random.choice(random_words)
+ "_"
+ random.choice(random_words)
+ "_"
+ random.choice(random_words)
)
with open("data/verification.txt", "w") as f:
f.write(self.verification)

async def reset_verification_channel(self, rules_channel, verification_channel):
self.generate_verification_phrase()

# Get all the rules from the rules channel.
rules_messages = []
number_of_rules = 0
async for message in rules_channel.history(limit=None, oldest_first=True):
if len(message.content.strip()) != 0:
number_of_rules += 1

rules_messages.append(message)

if number_of_rules == 0:
return

# Randomly choose which rule to inject the hidden message in.
random_rule = 0
if number_of_rules != 1:
if number_of_rules < 2:
random_rule = random.randint(0, number_of_rules - 1)
else:
# Don't include the first or last rule.
random_rule = random.randint(1, number_of_rules - 2)

# Delete all messages from the welcome channel.
await verification_channel.purge(limit=None)

# Put all rules in the welcome channel.
i = 0
for message in rules_messages:
content = message.content

if content.strip():
i += 1

if i == random_rule:
# Find all of the sentences in the random rule.
matches = list(re.finditer(r"[.|!|?][\W]*\s*", content))

# Randomly choose where to put the random message in our random rule.
random_sentence = 0
if len(matches) != 1:
random_sentence = random.randint(0, len(matches) - 1)

# Insert our verification text.
pos = matches[random_sentence].end()
content = (
content[:pos]
+ f' When you have finished reading all of the rules, send a message in this channel that includes "{self.verification}", and the bot will automatically grant you access to the other channels. '
+ content[pos:]
)

message_file = None
if len(message.attachments) != 0:
# Lists will only reupload a single image per message.
attachment = next(
(
a
for a in message.attachments
if os.path.splitext(a.filename)[1] in [".png", ".jpg", ".jpeg"]
),
None,
)
if attachment is not None:
message_file = discord.File(
io.BytesIO(await attachment.read()),
filename=attachment.filename,
)

await verification_channel.send(content=content, file=message_file)

# Commands

@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def reset(self, ctx):
"""Resets the verification channel with the latest rules"""

rules_channel_id = getattr(config, "rules_channel", 0)
verification_channel_id = getattr(config, "verification_channel", 0)

rules_channel = None
if rules_channel_id != 0:
rules_channel = self.bot.get_channel(rules_channel_id)

verification_channel = None
if verification_channel_id != 0:
verification_channel = self.bot.get_channel(verification_channel_id)

if rules_channel is not None and verification_channel is not None:
await self.reset_verification_channel(rules_channel, verification_channel)

@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def verifyall(self, ctx):
"""Gives everyone the verification role"""
verified_role = ctx.guild.get_role(config.verified_role)

for member in ctx.guild.members:
if verified_role not in member.roles:
await member.add_roles(verified_role)

await ctx.send("All members verified.")

@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def verifiedcount(self, ctx):
"""Prints the number of verified members"""
verified_role = ctx.guild.get_role(config.verified_role)
await ctx.send(
f"{ctx.guild.name} has " f"{len(verified_role.members)} verified members!"
)

# Listeners

@Cog.listener()
async def on_message(self, message):
await self.bot.wait_until_ready()

if not hasattr(config, "verification_channel"):
return

# We only care about messages in Rules, and Support FAQ
if message.channel.id != config.verification_channel:
return

# We don't care about messages from bots.
if message.author.bot:
return

await message.delete()

# We only care if the message contained the verification phrase.
if self.verification not in message.content:
return

# Grant user the verified role.
verified_role = message.guild.get_role(config.verified_role)
await message.author.add_roles(verified_role)

# Tasks

async def daily(self):
await self.bot.wait_until_ready()

if (
not hasattr(config, "log_channel")
or not hasattr(config, "rules_channel")
or not hasattr(config, "verification_channel")
):
return

log_channel = self.bot.get_channel(config.log_channel)
rules_channel = self.bot.get_channel(config.rules_channel)
verification_channel = self.bot.get_channel(config.verification_channel)

# Make sure the bot is open.
while not self.bot.is_closed():
# Reset the verification channel
try:
await self.reset_verification_channel(
rules_channel, verification_channel
)
except:
await log_channel.send(
"Verification reset has errored: ```" f"{traceback.format_exc()}```"
)

# Wait 1 day
await asyncio.sleep(86400)


def setup(bot):
bot.add_cog(ListsVerification(bot))

+ 0
- 90
cogs/lockdown.py View File

@@ -1,90 +0,0 @@
from discord.ext import commands
from discord.ext.commands import Cog
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):
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))
except:
pass

async def unlock_for_staff(self, channel: discord.TextChannel, issuer):
for role in config.staff_role_ids:
await self.set_sendmessage(channel, role, True, issuer)

@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
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."""
if not channel:
channel = ctx.channel
log_channel = self.bot.get_channel(config.log_channel)

roles = config.lockdown_configs["default"]["roles"]

for key, lockdown_conf in config.lockdown_configs.items():
if channel.id in lockdown_conf["channels"]:
roles = lockdown_conf["roles"]

for role in roles:
await self.set_sendmessage(channel, role, False, ctx.author)

await self.unlock_for_staff(channel, ctx.author)

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."

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}"
await log_channel.send(msg)

@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def unlock(self, ctx, channel: discord.TextChannel = None):
"""Unlocks speaking in current channel, staff only."""
if not channel:
channel = ctx.channel
log_channel = self.bot.get_channel(config.log_channel)

roles = config.lockdown_configs["default"]["roles"]

for key, lockdown_conf in config.lockdown_configs.items():
if channel.id in lockdown_conf["channels"]:
roles = lockdown_conf["roles"]

await self.unlock_for_staff(channel, ctx.author)

for role in roles:
await self.set_sendmessage(channel, role, True, ctx.author)

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}"
await log_channel.send(msg)


def setup(bot):
bot.add_cog(Lockdown(bot))

+ 0
- 353
cogs/logs.py View File

@@ -1,353 +0,0 @@
import discord
from discord.ext.commands import Cog
import json
import re
import config
from helpers.restrictions import get_user_restrictions
from helpers.checks import check_if_staff


class Logs(Cog):
"""
Logs join and leave messages, bans and unbans, and member changes.
"""

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.name_re = re.compile(r"[a-zA-Z0-9].*")
self.clean_re = re.compile(r'[^a-zA-Z0-9_ ]+', re.UNICODE)
# All lower case, no spaces, nothing non-alphanumeric
self.susp_words = ["tinfoil", "dz", # title managers
"blawar", "lithium", # title managers
"xci"] # "backup" format
susp_hellgex = "|".join([r"\W*".join(list(word)) for
word in self.susp_words])
self.susp_hellgex = re.compile(susp_hellgex, re.IGNORECASE)

self.ok_words = []

@Cog.listener()
async def on_member_join(self, member):
await self.bot.wait_until_ready()
log_channel = self.bot.get_channel(config.log_channel)
# We use this a lot, might as well get it once
escaped_name = self.bot.escape_message(member)

# Attempt to correlate the user joining with an invite
with open("data/invites.json", "r") as f:
invites = json.load(f)

real_invites = await member.guild.invites()

# Add unknown active invites. Can happen if invite was manually created
for invite in real_invites:
if invite.id not in invites:
invites[invite.id] = {
"uses": 0,
"url": invite.url,
"max_uses": invite.max_uses,
"code": invite.code
}

probable_invites_used = []
items_to_delete = []
# Look for invites whose usage increased since last lookup
for id, invite in invites.items():
real_invite = next((x for x in real_invites if x.id == id), None)

if real_invite is None:
# Invite does not exist anymore. Was either revoked manually
# or the final use was used up
probable_invites_used.append(invite)
items_to_delete.append(id)
elif invite["uses"] < real_invite.uses:
probable_invites_used.append(invite)
invite["uses"] = real_invite.uses

# Delete used up invites
for id in items_to_delete:
del invites[id]

# Save invites data.
with open("data/invites.json", "w") as f:
f.write(json.dumps(invites))

# Prepare the invite correlation message
if len(probable_invites_used) == 1:
invite_used = probable_invites_used[0]["code"]
elif len(probable_invites_used) == 0:
invite_used = "Unknown"
else:
invite_used = "One of: "
invite_used += ", ".join([x["code"] for x in probable_invites_used])

# Check if user account is older than 15 minutes
age = member.joined_at - member.created_at

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.
rsts = get_user_restrictions(member.id)
roles = [discord.utils.get(member.guild.roles, id=rst) for rst in rsts]
await member.add_roles(*roles)

# Real hell zone.
with open("data/userlog.json", "r") as f:
warns = json.load(f)
try:
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.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']}")
await log_channel.send(msg, embed=embed)
except KeyError: # if the user is not in the file
await log_channel.send(msg)

async def do_spy(self, message):
if message.author.bot:
return

if check_if_staff(message):
return

alert = False
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:
msg += f"\n- Has invite: https://{invite[0]}"
alert = True

for susp_word in self.susp_words:
if susp_word in cleancont and\
not any(ok_word in cleancont for ok_word in self.ok_words):
msg += f"\n- Contains suspicious word: `{susp_word}`"
alert = True

if alert:
msg += f"\n\nJump: <{message.jump_url}>"
spy_channel = self.bot.get_channel(config.spylog_channel)

# Bad Code :tm:, blame retr0id
message_clean = message.content.replace("*", "").replace("_", "")
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)

await spy_channel.send(msg, embed=embed)

async def do_nickcheck(self, message):
compliant = self.name_re.fullmatch(message.author.display_name)
if compliant:
return

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)

@Cog.listener()
async def on_message(self, message):
await self.bot.wait_until_ready()
if message.channel.id not in config.spy_channels:
return

await self.do_spy(message)

@Cog.listener()
async def on_message_edit(self, before, after):
await self.bot.wait_until_ready()
if after.channel.id not in config.spy_channels:
return

# If content is the same, just skip over it
# This usually means that something embedded.
if before.clean_content == after.clean_content:
return

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}`"

# If resulting message is too long, upload to hastebin
if len(msg) > 2000:
haste_url = await self.bot.haste(msg)
msg = f"📝 **Message edit**: \nToo long: <{haste_url}>"

await log_channel.send(msg)

@Cog.listener()
async def on_message_delete(self, message):
await self.bot.wait_until_ready()
if message.channel.id not in config.spy_channels:
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}`"

# If resulting message is too long, upload to hastebin
if len(msg) > 2000:
haste_url = await self.bot.haste(msg)
msg = f"🗑️ **Message delete**: \nToo long: <{haste_url}>"

await log_channel.send(msg)

@Cog.listener()
async def on_member_remove(self, member):
await self.bot.wait_until_ready()
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}"
await log_channel.send(msg)

@Cog.listener()
async def on_member_ban(self, guild, member):
await self.bot.wait_until_ready()
log_channel = self.bot.get_channel(config.log_channel)
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()
log_channel = self.bot.get_channel(config.log_channel)
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)
# with open("data/timebans.json", "r") as f:
# timebans = json.load(f)
# if user.id in timebans:
# timebans.pop(user.id)
# with open("data/timebans.json", "w") as f:
# json.dump(timebans, f)
await log_channel.send(msg)

@Cog.listener()
async def on_member_update(self, member_before, member_after):
await self.bot.wait_until_ready()
msg = ""
log_channel = self.bot.get_channel(config.log_channel)
if member_before.roles != member_after.roles:
# role removal
role_removal = []
for index, role in enumerate(member_before.roles):
if role not in member_after.roles:
role_removal.append(role)
# role addition
role_addition = []
for index, role in enumerate(member_after.roles):
if role not in member_before.roles:
role_addition.append(role)

if len(role_addition) != 0 or len(role_removal) != 0:
msg += "\n👑 __Role change__: "
roles = []
for role in role_removal:
roles.append("_~~" + role.name + "~~_")
for role in role_addition:
roles.append("__**" + role.name + "**__")
for index, role in enumerate(member_after.roles):
if role.name == "@everyone":
continue
if role not in role_removal and role not in role_addition:
roles.append(role.name)
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)}"
if member_before.nick != member_after.nick:
if not member_before.nick:
msg += "\n🏷 __Nickname addition__"
elif not member_after.nick:
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)}"
if msg:
msg = f"ℹ️ **Member update**: {member_after.id} | "\
f"{self.bot.escape_message(member_after)}{msg}"
await log_channel.send(msg)

async def report_reaction(self, payload, added):
await self.bot.wait_until_ready()
channel = self.bot.get_channel(payload.channel_id)
message = await channel.fetch_message(payload.message_id)
member = channel.guild.get_member(payload.user_id)
emoji = payload.emoji
# Don't log reactions from bots.
if message.author.bot:
return
# Don't log reactions from staff.
if any(r.id in config.staff_role_ids for r in member.roles):
return

log_channel = self.bot.get_channel(config.log_channel)
msg = ""
if added:
msg += "❤️ **Reaction added**: \n"
else:
msg += "💔 **Reaction removed**: \n"

msg += f"By {self.bot.escape_message(member.name)} "\
f"({member.id}), in {channel.mention}:\n"

if emoji.is_unicode_emoji():
msg += f"{emoji.name} on {message.jump_url}"

else:
msg += f"{self.bot.escape_message(emoji.name)} "\
f"({emoji.url}) on {message.jump_url}"

await log_channel.send(msg)

@Cog.listener()
async def on_raw_reaction_add(self, payload):
await self.report_reaction(payload, True)

@Cog.listener()
async def on_raw_reaction_remove(self, payload):
await self.report_reaction(payload, False)

def setup(bot):
bot.add_cog(Logs(bot))

+ 76
- 50
cogs/meme.py 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.guild_only()
@commands.check(check_if_verified)
@@ -43,9 +45,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.guild_only()
@commands.check(check_if_verified)
@@ -56,14 +60,16 @@ class Meme(Cog):

@commands.guild_only()
@commands.check(check_if_verified)
@commands.command(hidden=True, aliases=["atlassilver", "silv3r",
"atlassilv3r"])
@commands.command(hidden=True, aliases=["atlassilver", "silv3r", "atlassilv3r"])
async def silver(self, ctx, user: discord.Member):
"""Gives a user AtlasNX Silver™"""
embed = discord.Embed(title="AtlasNX Silver™!",
description=f"Here's your AtlasNX Silver™,"
f"{user.mention}!")
embed.set_image(url="https://cdn.discordapp.com/emojis/629188608732954635.png?v=1")
embed = discord.Embed(
title="AtlasNX Silver™!",
description=f"Here's your AtlasNX Silver™," f"{user.mention}!",
)
embed.set_image(
url="https://cdn.discordapp.com/emojis/629188608732954635.png?v=1"
)
await ctx.send(embed=embed)

@commands.check(check_if_verified)
@@ -71,9 +77,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_verified)
@commands.command(hidden=True)
@@ -91,25 +99,28 @@ 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_verified)
@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_verified)
@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_verified)
@commands.command(hidden=True, aliases=["when", "etawhen",
"thermosphere"])
@commands.command(hidden=True, aliases=["when", "etawhen", "thermosphere"])
async def eta(self, ctx):
await ctx.send("Never™")

@@ -132,40 +143,53 @@ class Meme(Cog):
async def frolics(self, ctx):
"""test"""
await ctx.send("https://www.youtube.com/watch?v=VmarNEsjpDI")
@commands.check(check_if_staff)
@commands.command(hidden=True, aliases=['bs', "biracy", ":b:iracy", "🅱iracy"])
@commands.command(hidden=True, aliases=["bs", "biracy", ":b:iracy", "🅱iracy"])
async def batches(self, ctx):
"""Yeet"""
await ctx.send("🅱or 🅱irated 🅱shop-🅱ames 🅱ou 🅱eed 🅱S 🅱ignature 🅱atches. 🅱s 🅱heir 🅱nly 🅱urpose 🅱s 🅱o 🅱llow 🅱iracy 🅱e\'re 🅱ot 🅱roviding 🅱ny 🅱elp 🅱ith 🅱nstallation 🅱f 🅱aid 🅱atches 🅱r 🅱irated 🅱ames 🅱fterwards")
@commands.check(check_if_staff)
await ctx.send(
"🅱or 🅱irated 🅱shop-🅱ames 🅱ou 🅱eed 🅱S 🅱ignature 🅱atches. 🅱s 🅱heir 🅱nly 🅱urpose 🅱s 🅱o 🅱llow 🅱iracy 🅱e're 🅱ot 🅱roviding 🅱ny 🅱elp 🅱ith 🅱nstallation 🅱f 🅱aid 🅱atches 🅱r 🅱irated 🅱ames 🅱fterwards"
)

@commands.check(check_if_staff)
@commands.comman