2023-03-09 22:01:10 +00:00
|
|
|
import asyncio
|
|
|
|
import logging.handlers
|
2018-03-08 22:47:53 +00:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
|
2023-03-09 22:01:10 +00:00
|
|
|
import aiohttp
|
2018-03-08 22:47:53 +00:00
|
|
|
import discord
|
|
|
|
from discord.ext import commands
|
2023-04-26 18:38:52 +00:00
|
|
|
from discord.ext.commands import CommandError, Context
|
2018-03-08 22:47:53 +00:00
|
|
|
|
2023-10-09 20:56:13 +00:00
|
|
|
from robocop_ng.helpers.notifications import report_critical_error
|
|
|
|
|
2023-04-05 10:10:18 +00:00
|
|
|
if len(sys.argv[1:]) != 1:
|
|
|
|
sys.stderr.write("usage: <state_dir>")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
state_dir = os.path.abspath(sys.argv[1])
|
|
|
|
sys.path.append(state_dir)
|
|
|
|
|
|
|
|
import config
|
2023-03-09 22:01:10 +00:00
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
script_name = os.path.basename(__file__).split(".")[0]
|
2018-03-08 22:47:53 +00:00
|
|
|
|
|
|
|
log_file_name = f"{script_name}.log"
|
|
|
|
|
|
|
|
# Limit of discord (non-nitro) is 8MB (not MiB)
|
|
|
|
max_file_size = 1000 * 1000 * 8
|
2018-12-23 23:25:30 +00:00
|
|
|
backup_count = 3
|
2018-03-08 22:47:53 +00:00
|
|
|
file_handler = logging.handlers.RotatingFileHandler(
|
2020-04-20 22:05:32 +00:00
|
|
|
filename=log_file_name, maxBytes=max_file_size, backupCount=backup_count
|
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
stdout_handler = logging.StreamHandler(sys.stdout)
|
|
|
|
|
|
|
|
log_format = logging.Formatter(
|
2020-04-20 22:05:32 +00:00
|
|
|
"[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s"
|
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
file_handler.setFormatter(log_format)
|
|
|
|
stdout_handler.setFormatter(log_format)
|
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
log = logging.getLogger("discord")
|
2018-03-08 22:47:53 +00:00
|
|
|
log.setLevel(logging.INFO)
|
|
|
|
log.addHandler(file_handler)
|
|
|
|
log.addHandler(stdout_handler)
|
|
|
|
|
|
|
|
|
|
|
|
def get_prefix(bot, message):
|
2018-12-23 13:13:39 +00:00
|
|
|
prefixes = config.prefixes
|
2018-03-08 22:47:53 +00:00
|
|
|
|
|
|
|
return commands.when_mentioned_or(*prefixes)(bot, message)
|
|
|
|
|
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
wanted_jsons = [
|
|
|
|
"data/restrictions.json",
|
|
|
|
"data/robocronptab.json",
|
|
|
|
"data/userlog.json",
|
|
|
|
"data/invites.json",
|
2023-03-30 17:03:17 +00:00
|
|
|
"data/macros.json",
|
2023-04-01 16:43:56 +00:00
|
|
|
"data/persistent_roles.json",
|
2023-05-02 19:38:22 +00:00
|
|
|
"data/disabled_ids.json",
|
2020-04-20 22:05:32 +00:00
|
|
|
]
|
2018-12-27 11:44:09 +00:00
|
|
|
|
2024-01-06 14:33:01 +00:00
|
|
|
if not os.path.exists(os.path.join(state_dir, "data")):
|
|
|
|
os.makedirs(os.path.join(state_dir, "data"))
|
|
|
|
|
2023-04-05 10:10:18 +00:00
|
|
|
for wanted_json_idx in range(len(wanted_jsons)):
|
|
|
|
wanted_jsons[wanted_json_idx] = os.path.join(
|
|
|
|
state_dir, wanted_jsons[wanted_json_idx]
|
|
|
|
)
|
2023-10-09 20:56:13 +00:00
|
|
|
if not os.path.isfile(wanted_jsons[wanted_json_idx]):
|
|
|
|
with open(wanted_jsons[wanted_json_idx], "w") as file:
|
|
|
|
file.write("{}")
|
2023-04-05 10:10:18 +00:00
|
|
|
|
2022-05-24 21:26:24 +00:00
|
|
|
intents = discord.Intents.all()
|
2020-10-01 19:07:50 +00:00
|
|
|
intents.typing = False
|
|
|
|
|
2020-10-02 00:03:41 +00:00
|
|
|
bot = commands.Bot(
|
|
|
|
command_prefix=get_prefix, description=config.bot_description, intents=intents
|
|
|
|
)
|
2019-11-05 23:06:21 +00:00
|
|
|
bot.help_command = commands.DefaultHelpCommand(dm_help=True)
|
2018-03-08 22:47:53 +00:00
|
|
|
|
|
|
|
bot.log = log
|
|
|
|
bot.config = config
|
|
|
|
bot.script_name = script_name
|
2023-04-05 10:10:18 +00:00
|
|
|
bot.state_dir = state_dir
|
2018-12-27 11:44:09 +00:00
|
|
|
bot.wanted_jsons = wanted_jsons
|
2018-03-08 22:47:53 +00:00
|
|
|
|
2023-03-09 22:01:10 +00:00
|
|
|
|
2023-04-24 06:21:04 +00:00
|
|
|
async def get_channel_safe(self, channel_id: int):
|
|
|
|
res = self.get_channel(channel_id)
|
2023-03-03 21:20:05 +00:00
|
|
|
if res is None:
|
2023-04-24 06:21:04 +00:00
|
|
|
res = await self.fetch_channel(channel_id)
|
2023-03-03 21:20:05 +00:00
|
|
|
|
|
|
|
return res
|
|
|
|
|
2023-03-09 22:01:10 +00:00
|
|
|
|
2023-03-03 21:20:05 +00:00
|
|
|
commands.Bot.get_channel_safe = get_channel_safe
|
2018-03-08 22:47:53 +00:00
|
|
|
|
2023-03-09 22:01:10 +00:00
|
|
|
|
2018-03-08 22:47:53 +00:00
|
|
|
@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()
|
2023-03-03 21:20:05 +00:00
|
|
|
bot.botlog_channel = await bot.get_channel_safe(config.botlog_channel)
|
2018-03-08 22:47:53 +00:00
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
log.info(
|
|
|
|
f"\nLogged in as: {bot.user.name} - "
|
|
|
|
f"{bot.user.id}\ndpy version: {discord.__version__}\n"
|
|
|
|
)
|
2018-12-23 13:13:39 +00:00
|
|
|
game_name = f"{config.prefixes[0]}help"
|
2018-12-24 08:23:14 +00:00
|
|
|
|
|
|
|
# Send "Robocop has started! x has y members!"
|
2018-12-30 23:05:40 +00:00
|
|
|
guild = bot.botlog_channel.guild
|
2020-04-20 22:05:32 +00:00
|
|
|
msg = (
|
|
|
|
f"{bot.user.name} has started! "
|
|
|
|
f"{guild.name} has {guild.member_count} members!"
|
|
|
|
)
|
2018-12-27 11:44:09 +00:00
|
|
|
|
2018-12-29 07:47:08 +00:00
|
|
|
data_files = [discord.File(fpath) for fpath in wanted_jsons]
|
2018-12-30 23:05:40 +00:00
|
|
|
await bot.botlog_channel.send(msg, files=data_files)
|
2018-12-24 08:23:14 +00:00
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
activity = discord.Activity(name=game_name, type=discord.ActivityType.listening)
|
2019-01-01 01:46:42 +00:00
|
|
|
|
|
|
|
await bot.change_presence(activity=activity)
|
2018-03-08 22:47:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
@bot.event
|
|
|
|
async def on_command(ctx):
|
2020-04-20 22:05:32 +00:00
|
|
|
log_text = (
|
|
|
|
f"{ctx.message.author} ({ctx.message.author.id}): " f'"{ctx.message.content}" '
|
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
if ctx.guild: # was too long for tertiary if
|
2020-04-20 22:05:32 +00:00
|
|
|
log_text += (
|
|
|
|
f'on "{ctx.channel.name}" ({ctx.channel.id}) '
|
|
|
|
f'at "{ctx.guild.name}" ({ctx.guild.id})'
|
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
else:
|
|
|
|
log_text += f"on DMs ({ctx.channel.id})"
|
|
|
|
log.info(log_text)
|
|
|
|
|
|
|
|
|
|
|
|
@bot.event
|
2023-04-26 18:38:52 +00:00
|
|
|
async def on_error(event: str, *args, **kwargs):
|
|
|
|
log.exception(f"Error on {event}:")
|
2018-03-08 22:47:53 +00:00
|
|
|
|
2024-01-06 14:33:01 +00:00
|
|
|
exception = sys.exc_info()[1]
|
2023-10-09 20:56:13 +00:00
|
|
|
is_report_allowed = any(
|
|
|
|
[
|
|
|
|
not isinstance(exception, x)
|
|
|
|
for x in [
|
|
|
|
discord.RateLimited,
|
|
|
|
discord.GatewayNotFound,
|
|
|
|
discord.InteractionResponded,
|
|
|
|
discord.LoginFailure,
|
|
|
|
]
|
|
|
|
]
|
|
|
|
)
|
|
|
|
if exception is not None and is_report_allowed:
|
|
|
|
await report_critical_error(
|
|
|
|
bot,
|
|
|
|
exception,
|
|
|
|
additional_info={"Event": event, "args": args, "kwargs": kwargs},
|
|
|
|
)
|
|
|
|
|
2018-03-08 22:47:53 +00:00
|
|
|
|
|
|
|
@bot.event
|
2023-04-26 18:38:52 +00:00
|
|
|
async def on_command_error(ctx: Context, error: CommandError):
|
2018-12-26 07:48:41 +00:00
|
|
|
error_text = str(error)
|
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
err_msg = (
|
|
|
|
f'Error with "{ctx.message.content}" from '
|
|
|
|
f'"{ctx.message.author} ({ctx.message.author.id}) '
|
|
|
|
f"of type {type(error)}: {error_text}"
|
|
|
|
)
|
2018-12-30 23:05:40 +00:00
|
|
|
|
2023-05-01 19:02:37 +00:00
|
|
|
log.exception(err_msg)
|
2018-12-30 23:05:40 +00:00
|
|
|
|
2019-01-07 08:51:27 +00:00
|
|
|
if not isinstance(error, commands.CommandNotFound):
|
|
|
|
err_msg = bot.escape_message(err_msg)
|
|
|
|
await bot.botlog_channel.send(err_msg)
|
2018-03-08 22:47:53 +00:00
|
|
|
|
|
|
|
if isinstance(error, commands.NoPrivateMessage):
|
2019-02-22 17:09:58 +00:00
|
|
|
return await ctx.send("This command doesn't work on DMs.")
|
2018-03-08 22:47:53 +00:00
|
|
|
elif isinstance(error, commands.MissingPermissions):
|
2020-04-20 22:05:32 +00:00
|
|
|
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}```"
|
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
elif isinstance(error, commands.BotMissingPermissions):
|
2020-04-20 22:05:32 +00:00
|
|
|
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}```"
|
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
elif isinstance(error, commands.CommandOnCooldown):
|
2020-04-20 22:05:32 +00:00
|
|
|
return await ctx.send(
|
|
|
|
f"{ctx.author.mention}: You're being "
|
|
|
|
"ratelimited. Try in "
|
|
|
|
f"{error.retry_after:.1f} seconds."
|
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
elif isinstance(error, commands.CheckFailure):
|
2020-04-20 22:05:32 +00:00
|
|
|
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 (
|
2023-04-01 16:43:56 +00:00
|
|
|
"Cannot send messages to this user" in error_text
|
2020-04-20 22:05:32 +00:00
|
|
|
):
|
|
|
|
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."
|
|
|
|
)
|
2018-12-23 23:25:30 +00:00
|
|
|
elif isinstance(error, commands.CommandNotFound):
|
|
|
|
# Nothing to do when command is not found.
|
|
|
|
return
|
2018-03-08 22:47:53 +00:00
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
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."
|
|
|
|
)
|
2020-06-26 23:01:38 +00:00
|
|
|
|
|
|
|
# Keep a list of commands that involve mentioning users
|
|
|
|
# and can involve users leaving/getting banned
|
2023-03-09 22:01:10 +00:00
|
|
|
# noinspection NonAsciiCharacters,PyPep8Naming
|
2020-06-26 23:01:38 +00:00
|
|
|
ಠ_ಠ = ["warn", "kick", "ban"]
|
|
|
|
|
2018-03-08 22:47:53 +00:00
|
|
|
if isinstance(error, commands.BadArgument):
|
2020-06-26 23:01:38 +00:00
|
|
|
# and if said commands get used, add a specific notice.
|
|
|
|
if ctx.command.name in ಠ_ಠ:
|
|
|
|
help_text = (
|
2023-04-01 16:43:56 +00:00
|
|
|
"This probably means that user left (or already got kicked/banned).\n"
|
|
|
|
+ help_text
|
2020-06-26 23:01:38 +00:00
|
|
|
)
|
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
return await ctx.send(
|
2020-06-26 23:01:38 +00:00
|
|
|
f"{ctx.author.mention}: You gave incorrect arguments. {help_text}"
|
2020-04-20 22:05:32 +00:00
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
elif isinstance(error, commands.MissingRequiredArgument):
|
2020-04-20 22:05:32 +00:00
|
|
|
return await ctx.send(
|
2020-06-26 23:01:38 +00:00
|
|
|
f"{ctx.author.mention}: You gave incomplete arguments. {help_text}"
|
2020-04-20 22:05:32 +00:00
|
|
|
)
|
2018-03-08 22:47:53 +00:00
|
|
|
|
|
|
|
|
|
|
|
@bot.event
|
|
|
|
async def on_message(message):
|
|
|
|
if message.author.bot:
|
|
|
|
return
|
|
|
|
|
2018-12-25 12:11:06 +00:00
|
|
|
if (message.guild) and (message.guild.id not in config.guild_whitelist):
|
2018-12-23 13:13:39 +00:00
|
|
|
return
|
|
|
|
|
2019-04-25 07:17:06 +00:00
|
|
|
# Ignore messages in newcomers channel, unless it's potentially
|
|
|
|
# an allowed command
|
|
|
|
welcome_allowed = ["reset", "kick", "ban", "warn"]
|
2020-04-20 22:05:32 +00:00
|
|
|
if message.channel.id == config.welcome_channel and not any(
|
2023-04-01 16:43:56 +00:00
|
|
|
cmd in message.content for cmd in welcome_allowed
|
2020-04-20 22:05:32 +00:00
|
|
|
):
|
2019-01-22 21:09:21 +00:00
|
|
|
return
|
|
|
|
|
2018-03-08 22:47:53 +00:00
|
|
|
ctx = await bot.get_context(message)
|
|
|
|
await bot.invoke(ctx)
|
|
|
|
|
2020-04-20 22:05:32 +00:00
|
|
|
|
2022-05-24 21:12:20 +00:00
|
|
|
async def main():
|
|
|
|
async with bot:
|
2023-09-11 19:24:23 +00:00
|
|
|
if len(config.guild_whitelist) == 1:
|
|
|
|
invite_url = discord.utils.oauth_url(
|
|
|
|
config.client_id,
|
|
|
|
guild=discord.Object(config.guild_whitelist[0]),
|
|
|
|
disable_guild_select=True,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
invite_url = discord.utils.oauth_url(config.client_id)
|
|
|
|
|
|
|
|
log.info(f"\nInvite URL: {invite_url}\n")
|
|
|
|
|
2022-05-24 21:12:20 +00:00
|
|
|
for cog in config.initial_cogs:
|
|
|
|
try:
|
2023-09-11 19:24:23 +00:00
|
|
|
await bot.load_extension(f"robocop_ng.{cog}")
|
2023-04-26 18:38:52 +00:00
|
|
|
except Exception as e:
|
|
|
|
log.exception(f"Failed to load cog {cog}:", e)
|
2022-05-24 21:12:20 +00:00
|
|
|
await bot.start(config.token)
|
|
|
|
|
2022-05-24 21:29:46 +00:00
|
|
|
|
2022-05-24 21:12:20 +00:00
|
|
|
if __name__ == "__main__":
|
|
|
|
asyncio.run(main())
|