2019-10-03 22:17:36 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
2019-10-04 11:39:12 -04:00
|
|
|
qrm, a bot for Discord
|
2019-10-03 22:17:36 -04:00
|
|
|
---
|
2019-10-05 02:23:11 -04:00
|
|
|
Copyright (C) 2019 Abigail Gold, 0x5c
|
2019-10-03 22:17:36 -04:00
|
|
|
|
2019-10-05 02:23:11 -04:00
|
|
|
This file is part of discord-qrmbot and is released under the terms of the GNU
|
|
|
|
General Public License, version 2.
|
2019-10-03 22:17:36 -04:00
|
|
|
"""
|
|
|
|
|
2019-10-04 08:53:05 -04:00
|
|
|
from types import SimpleNamespace
|
|
|
|
|
2019-10-03 22:17:36 -04:00
|
|
|
import discord
|
2019-10-04 14:17:45 -04:00
|
|
|
from discord.ext import commands, tasks
|
2019-10-03 22:17:36 -04:00
|
|
|
|
|
|
|
import info
|
|
|
|
|
|
|
|
import options as opt
|
|
|
|
import keys
|
|
|
|
|
|
|
|
|
2019-10-06 22:42:44 -04:00
|
|
|
# --- Settings ---
|
|
|
|
|
|
|
|
exit_code = 1 # The default exit code. ?shutdown and ?restart will change it accordingly (fail-safe)
|
2019-10-04 08:53:05 -04:00
|
|
|
|
|
|
|
debug_mode = opt.debug # Separate assignement in-case we define an override (ternary operator goes here)
|
|
|
|
|
|
|
|
|
2019-10-04 13:10:27 -04:00
|
|
|
class GlobalSettings(commands.Cog):
|
2019-10-18 08:27:05 -04:00
|
|
|
def __init__(self, bot: commands.Bot):
|
2019-10-04 13:10:27 -04:00
|
|
|
self.bot = bot
|
|
|
|
|
|
|
|
self.opt = opt
|
|
|
|
self.keys = keys
|
|
|
|
self.info = info
|
|
|
|
|
2019-10-08 18:39:05 -04:00
|
|
|
self.colours = SimpleNamespace(good=0x43B581,
|
|
|
|
neutral=0x7289DA,
|
|
|
|
bad=0xF04747)
|
2019-10-04 13:10:27 -04:00
|
|
|
self.debug = debug_mode
|
2019-10-04 08:53:05 -04:00
|
|
|
|
|
|
|
|
|
|
|
# --- Bot setup ---
|
|
|
|
|
2019-10-08 18:39:05 -04:00
|
|
|
bot = commands.Bot(command_prefix=opt.prefix,
|
|
|
|
description=info.description,
|
|
|
|
help_command=commands.MinimalHelpCommand())
|
2019-10-04 08:53:05 -04:00
|
|
|
|
2019-10-18 08:27:05 -04:00
|
|
|
|
2019-10-07 09:11:03 -04:00
|
|
|
# --- Helper functions ---
|
|
|
|
|
|
|
|
async def add_react(msg: discord.Message, react: str):
|
|
|
|
try:
|
|
|
|
await msg.add_reaction(react)
|
|
|
|
except discord.Forbidden:
|
|
|
|
print(f"!! Missing permissions to add reaction in '{msg.guild.id}/{msg.channel.id}'!")
|
|
|
|
|
|
|
|
|
|
|
|
# --- Checks ---
|
|
|
|
|
|
|
|
async def check_if_owner(ctx: commands.Context):
|
|
|
|
if ctx.author.id in opt.owners_uids:
|
|
|
|
return True
|
2019-10-18 08:27:05 -04:00
|
|
|
await add_react(ctx.message, "❌")
|
|
|
|
return False
|
2019-10-07 09:11:03 -04:00
|
|
|
|
|
|
|
|
2019-10-04 08:53:05 -04:00
|
|
|
# --- Commands ---
|
2019-10-03 22:17:36 -04:00
|
|
|
|
2019-10-06 22:42:44 -04:00
|
|
|
@bot.command(name="restart")
|
2019-10-07 09:11:03 -04:00
|
|
|
@commands.check(check_if_owner)
|
2019-10-18 08:27:05 -04:00
|
|
|
async def _restart_bot(ctx: commands.Context):
|
2019-10-06 22:42:44 -04:00
|
|
|
"""Restarts the bot."""
|
|
|
|
global exit_code
|
2019-10-07 09:11:03 -04:00
|
|
|
await add_react(ctx.message, "✅")
|
|
|
|
exit_code = 42 # Signals to the wrapper script that the bot needs to be restarted.
|
|
|
|
await bot.logout()
|
|
|
|
|
2019-10-06 22:42:44 -04:00
|
|
|
|
|
|
|
@bot.command(name="shutdown")
|
2019-10-07 09:11:03 -04:00
|
|
|
@commands.check(check_if_owner)
|
2019-10-18 08:27:05 -04:00
|
|
|
async def _shutdown_bot(ctx: commands.Context):
|
2019-10-06 22:42:44 -04:00
|
|
|
"""Shuts down the bot."""
|
|
|
|
global exit_code
|
2019-10-07 09:11:03 -04:00
|
|
|
await add_react(ctx.message, "✅")
|
|
|
|
exit_code = 0 # Signals to the wrapper script that the bot should not be restarted.
|
|
|
|
await bot.logout()
|
2019-10-06 22:42:44 -04:00
|
|
|
|
2019-10-04 13:10:27 -04:00
|
|
|
|
|
|
|
# --- Events ---
|
|
|
|
|
|
|
|
@bot.event
|
|
|
|
async def on_ready():
|
|
|
|
print(f"Logged in as: {bot.user} - {bot.user.id}")
|
|
|
|
print("------")
|
2019-10-04 14:17:45 -04:00
|
|
|
|
|
|
|
|
|
|
|
# --- Tasks ---
|
|
|
|
|
|
|
|
@tasks.loop(minutes=5)
|
|
|
|
async def _ensure_activity():
|
2019-10-08 23:02:57 -04:00
|
|
|
await bot.change_presence(activity=discord.Game(name=opt.game))
|
2019-10-04 13:10:27 -04:00
|
|
|
|
|
|
|
|
2019-10-04 14:17:45 -04:00
|
|
|
@_ensure_activity.before_loop
|
|
|
|
async def _before_ensure_activity():
|
|
|
|
await bot.wait_until_ready()
|
|
|
|
|
|
|
|
|
2019-10-04 13:10:27 -04:00
|
|
|
# --- Run ---
|
|
|
|
|
|
|
|
bot.add_cog(GlobalSettings(bot))
|
2019-10-08 23:02:57 -04:00
|
|
|
for cog in opt.cogs:
|
|
|
|
bot.load_extension(f"cogs.{cog}")
|
2019-10-03 22:17:36 -04:00
|
|
|
|
2019-10-04 14:17:45 -04:00
|
|
|
_ensure_activity.start()
|
|
|
|
|
|
|
|
|
2019-10-03 23:08:08 -04:00
|
|
|
try:
|
|
|
|
bot.run(keys.discord_token)
|
|
|
|
|
2019-10-08 18:39:05 -04:00
|
|
|
except discord.LoginFailure as ex:
|
|
|
|
# Miscellaneous authentications errors: borked token and co
|
2019-10-03 23:08:08 -04:00
|
|
|
if debug_mode:
|
|
|
|
raise
|
|
|
|
raise SystemExit("Error: Failed to authenticate: {}".format(ex))
|
|
|
|
|
2019-10-08 18:39:05 -04:00
|
|
|
except discord.ConnectionClosed as ex:
|
|
|
|
# When the connection to the gateway (websocket) is closed
|
2019-10-03 23:08:08 -04:00
|
|
|
if debug_mode:
|
|
|
|
raise
|
|
|
|
raise SystemExit("Error: Discord gateway connection closed: [Code {}] {}".format(ex.code, ex.reason))
|
|
|
|
|
2019-10-08 18:39:05 -04:00
|
|
|
except ConnectionResetError as ex:
|
|
|
|
# More generic connection reset error
|
2019-10-03 23:08:08 -04:00
|
|
|
if debug_mode:
|
|
|
|
raise
|
|
|
|
raise SystemExit("ConnectionResetError: {}".format(ex))
|
2019-10-06 22:42:44 -04:00
|
|
|
|
|
|
|
# --- Exit ---
|
|
|
|
# Codes for the wrapper shell script:
|
|
|
|
# 0 - Clean exit, don't restart
|
|
|
|
# 1 - Error exit, [restarting is up to the shell script]
|
|
|
|
# 42 - Clean exit, do restart
|
|
|
|
|
|
|
|
raise SystemExit(exit_code)
|