Owner cog / Version command / doc link update / OAUTH (#203)

* Version command (#183)

* reword. replace link with link to our doc

* Move all owner commands to a separate plugin (#188)

* Move all owner commands to their own plugin.

* Move all initial cog loading to Owner plugin

* Final fix of initial cog loading

* don't allow people to unload the owner plugin without reloading it

* make sure we modify the cog registry like we're supposed to

* log the functionname too

Message updates, grammar, politeness etc

Get right names in logging formatter

Ignore cogs.owner if we find it

* add version back in

add reload docstring

Heh, woops...little security bug here

* Add in globals and bot to locals

* pass exception to the logger

* Bot will now generate OAUTH URL from the supplied endpoint (#196)

* Formatting changes, revert uptime

* Store the oauth_url internally, provide it if the owner uses `!join`
This commit is contained in:
Will 2016-04-28 16:33:53 -04:00
parent 81409271f5
commit 5b764c41c3
2 changed files with 526 additions and 322 deletions

395
cogs/owner.py Normal file
View File

@ -0,0 +1,395 @@
import discord
from discord.ext import commands
from cogs.utils import checks
from __main__ import set_cog, send_cmd_help, settings
import importlib
import traceback
import logging
import asyncio
import threading
import datetime
import glob
import os
import time
log = logging.getLogger("red.owner")
class CogNotFoundError(Exception):
pass
class CogLoadError(Exception):
pass
class NoSetupError(CogLoadError):
pass
class CogUnloadError(Exception):
pass
class OwnerUnloadWithoutReloadError(CogUnloadError):
pass
class Owner:
"""All owner-only commands that relate to debug bot operations.
"""
def __init__(self, bot):
self.bot = bot
self.setowner_lock = False
@commands.command()
@checks.is_owner()
async def load(self, *, module: str):
"""Loads a module
Example: load mod"""
module = module.strip()
if "cogs." not in module:
module = "cogs." + module
try:
self._load_cog(module)
except CogNotFoundError:
await self.bot.say("That module could not be found.")
except CogLoadError as e:
log.exception(e)
traceback.print_exc()
await self.bot.say("There was an issue loading the module."
" Check your logs for more information.")
except Exception as e:
log.exception(e)
traceback.print_exc()
await self.bot.say('Module was found and possibly loaded but '
'something went wrong.'
' Check your logs for more information.')
else:
set_cog(module, True)
await self.bot.say("Module enabled.")
@commands.command()
@checks.is_owner()
async def unload(self, *, module: str):
"""Unloads a module
Example: unload mod"""
module = module.strip()
if "cogs." not in module:
module = "cogs." + module
if not self._does_cogfile_exist(module):
await self.bot.say("That module file doesn't exist. I will not"
" turn off autoloading at start just in case"
" this isn't supposed to happen.")
else:
set_cog(module, False)
try: # No matter what we should try to unload it
self._unload_cog(module)
except OwnerUnloadWithoutReloadError:
await self.bot.say("I cannot allow you to unload the Owner plugin"
" unless you are in the process of reloading.")
except CogUnloadError as e:
log.exception(e)
traceback.print_exc()
await self.bot.say('Unable to safely disable that module.')
else:
await self.bot.say("Module disabled.")
@checks.is_owner()
@commands.command(name="reload")
async def _reload(self, module):
"""Reloads a module
Example: reload audio"""
if "cogs." not in module:
module = "cogs." + module
try:
self._unload_cog(module, reloading=True)
except:
pass
try:
self._load_cog(module)
except CogNotFoundError:
await self.bot.say("That module cannot be found.")
except NoSetupError:
await self.bot.say("That module does not have a setup function.")
except CogLoadError as e:
log.exception(e)
traceback.print_exc()
await self.bot.say("That module could not be loaded. Check your"
" logs for more information.")
else:
set_cog(module, True)
await self.bot.say("Module reloaded.")
@commands.command(pass_context=True, hidden=True)
@checks.is_owner()
async def debug(self, ctx, *, code):
"""Evaluates code
Modified function, originally made by Rapptz"""
code = code.strip('` ')
python = '```py\n{}\n```'
result = None
local_vars = locals().copy()
local_vars['bot'] = self.bot
try:
result = eval(code, globals(), local_vars)
except Exception as e:
await self.bot.say(python.format(type(e).__name__ + ': ' + str(e)))
return
if asyncio.iscoroutine(result):
result = await result
result = python.format(result)
if not ctx.message.channel.is_private:
censor = (settings.email, settings.password)
r = "[EXPUNGED]"
for w in censor:
if w != "":
result = result.replace(w, r)
result = result.replace(w.lower(), r)
result = result.replace(w.upper(), r)
await self.bot.say(result)
@commands.group(name="set", pass_context=True)
async def _set(self, ctx):
"""Changes Red's global settings."""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
return
@_set.command(pass_context=True)
async def owner(self, ctx):
"""Sets owner"""
if settings.owner != "id_here":
await self.bot.say("Owner ID has already been set.")
return
if self.setowner_lock:
await self.bot.say("A set owner command is already pending.")
return
await self.bot.say("Confirm in the console that you're the owner.")
self.setowner_lock = True
t = threading.Thread(target=self._wait_for_answer,
args=(ctx.message.author,))
t.start()
@_set.command()
@checks.is_owner()
async def prefix(self, *prefixes):
"""Sets prefixes
Must be separated by a space. Enclose in double
quotes if a prefix contains spaces."""
if prefixes == ():
await self.bot.say("Example: setprefix [ ! ^ .")
return
self.bot.command_prefix = sorted(prefixes, reverse=True)
settings.prefixes = sorted(prefixes, reverse=True)
log.debug("Setting prefixes to:\n\t{}".format(settings.prefixes))
if len(prefixes) > 1:
await self.bot.say("Prefixes set")
else:
await self.bot.say("Prefix set")
@_set.command(pass_context=True)
@checks.is_owner()
async def name(self, ctx, *, name):
"""Sets Red's name"""
name = name.strip()
if name == "":
await send_cmd_help(ctx)
await self.bot.edit_profile(settings.password, username=name)
await self.bot.say("Done.")
@_set.command(pass_context=True)
@checks.is_owner()
async def status(self, ctx, *, status=None):
"""Sets Red's status
Leaving this empty will clear it."""
if status:
status = status.strip()
await self.bot.change_status(discord.Game(name=status))
log.debug('Status set to "{}" by owner'.format(status))
else:
await self.bot.change_status(None)
log.debug('status cleared by owner')
await self.bot.say("Done.")
@_set.command()
@checks.is_owner()
async def avatar(self, url):
"""Sets Red's avatar"""
try:
async with self.bot.session.get(url) as r:
data = await r.read()
await self.bot.edit_profile(settings.password, avatar=data)
await self.bot.say("Done.")
log.debug("changed avatar")
except Exception as e:
await self.bot.say("Error, check your logs for more information.")
log.exception(e)
traceback.print_exc()
@_set.command(name="token")
@checks.is_owner()
async def _token(self, token):
"""Sets Red's login token"""
if len(token) < 50:
await self.bot.say("Invalid token.")
else:
settings.login_type = "token"
settings.email = token
settings.password = ""
await self.bot.say("Token set. Restart me.")
log.debug("Just converted to a bot account.")
@commands.command()
@checks.is_owner()
async def shutdown(self):
"""Shuts down Red"""
await self.bot.logout()
@commands.command()
@checks.is_owner()
async def join(self, invite_url: discord.Invite=None):
"""Joins new server"""
if hasattr(self.bot.user, 'bot') and self.bot.user.bot is True:
# Check to ensure they're using updated discord.py
msg = ("I have a **BOT** tag, so I must be invited with an OAuth2"
" link:\nFor more information: "
"https://twentysix26.github.io/"
"Red-Docs/red_guide_bot_accounts/#bot-invites")
await self.bot.say(msg)
if hasattr(self.bot, 'oauth_url'):
await self.bot.whisper("Here's my OAUTH2 link:\n{}".format(
self.bot.oauth_url))
return
if invite_url is None:
await self.bot.say("I need a Discord Invite link for the "
"server you want me to join.")
return
try:
await self.bot.accept_invite(invite_url)
await self.bot.say("Server joined.")
log.debug("We just joined {}".format(invite_url))
except discord.NotFound:
await self.bot.say("The invite was invalid or expired.")
except discord.HTTPException:
await self.bot.say("I wasn't able to accept the invite."
" Try again.")
@commands.command(pass_context=True)
@checks.is_owner()
async def leave(self, ctx):
"""Leaves server"""
message = ctx.message
await self.bot.say("Are you sure you want me to leave this server?"
" Type yes to confirm.")
response = await self.bot.wait_for_message(author=message.author)
if response.content.lower().strip() == "yes":
await self.bot.say("Alright. Bye :wave:")
log.debug('Leaving "{}"'.format(message.server.name))
await self.bot.leave_server(message.server)
else:
await self.bot.say("Ok I'll stay here then.")
@commands.command()
async def uptime(self):
"""Shows Red's uptime"""
up = abs(self.bot.uptime - int(time.perf_counter()))
up = str(datetime.timedelta(seconds=up))
await self.bot.say("`Uptime: {}`".format(up))
@commands.command()
async def version(self):
"""Shows Red's current version"""
response = self.bot.loop.run_in_executor(None, self._get_version)
result = await asyncio.wait_for(response, timeout=10)
await self.bot.say(result)
def _load_cog(self, cogname):
if not self._does_cogfile_exist(cogname):
raise CogNotFoundError(cogname)
try:
mod_obj = importlib.import_module(cogname)
importlib.reload(mod_obj)
self.bot.load_extension(mod_obj.__name__)
except discord.ClientException:
raise NoSetupError
except SyntaxError as e:
raise CogLoadError(*e.args)
def _unload_cog(self, cogname, reloading=False):
if not reloading and cogname == "cogs.owner":
raise OwnerUnloadWithoutReloadError(
"Can't unload the owner plugin :P")
try:
self.bot.unload_extension(cogname)
except:
raise CogUnloadError
def _list_cogs(self):
cogs = glob.glob("cogs/*.py")
clean = []
for c in cogs:
c = c.replace("/", "\\") # Linux fix
clean.append("cogs." + c.split("\\")[1].replace(".py", ""))
return clean
def _does_cogfile_exist(self, module):
if "cogs." not in module:
module = "cogs." + module
if module not in self._list_cogs():
return False
return True
def _wait_for_answer(self, author):
print(author.name + " requested to be set as owner. If this is you, "
"type 'yes'. Otherwise press enter.")
print()
print("*DO NOT* set anyone else as owner.")
choice = "None"
while choice.lower() != "yes" and choice == "None":
choice = input("> ")
if choice == "yes":
settings.owner = author.id
print(author.name + " has been set as owner.")
self.setowner_lock = False
self.owner.hidden = True
else:
print("setowner request has been ignored.")
self.setowner_lock = False
def _get_version(self):
getversion = os.popen(r'git show -s HEAD --format="%cr|%s|%h"')
getversion = getversion.read()
version = getversion.split('|')
return 'Last updated: ``{}``\nCommit: ``{}``\nHash: ``{}``'.format(
*version)
def setup(bot):
n = Owner(bot)
bot.add_cog(n)

453
red.py
View File

@ -1,18 +1,12 @@
from discord.ext import commands from discord.ext import commands
import discord import discord
from cogs.utils.settings import Settings from cogs.utils.settings import Settings
from random import choice as rndchoice import json
import threading import asyncio
import datetime, re
import json, asyncio
import copy
import glob
import os import os
import time import time
import sys import sys
import logging import logging
import aiohttp
import importlib
import shutil import shutil
import traceback import traceback
@ -38,7 +32,6 @@ settings = Settings()
from cogs.utils import checks from cogs.utils import checks
lock = False
@bot.event @bot.event
async def on_ready(): async def on_ready():
@ -53,17 +46,28 @@ async def on_ready():
print(servers + " servers") print(servers + " servers")
print(channels + " channels") print(channels + " channels")
print(users + " users") print(users + " users")
print("\n{0} active cogs with {1} commands\n".format(str(len(bot.cogs)), str(len(bot.commands)))) print("\n{0} active cogs with {1} commands\n".format(
str(len(bot.cogs)), str(len(bot.commands))))
if settings.login_type == "token":
print("------")
print("Use this url to bring your bot to a server:")
url = await get_oauth_url()
bot.oauth_url = url
print(url)
print("------")
@bot.event @bot.event
async def on_command(command, ctx): async def on_command(command, ctx):
pass pass
@bot.event @bot.event
async def on_message(message): async def on_message(message):
if user_allowed(message): if user_allowed(message):
await bot.process_commands(message) await bot.process_commands(message)
@bot.event @bot.event
async def on_command_error(error, ctx): async def on_command_error(error, ctx):
if isinstance(error, commands.MissingRequiredArgument): if isinstance(error, commands.MissingRequiredArgument):
@ -81,236 +85,6 @@ async def send_cmd_help(ctx):
for page in pages: for page in pages:
await bot.send_message(ctx.message.channel, page) await bot.send_message(ctx.message.channel, page)
@bot.command()
@checks.is_owner()
async def load(*, module : str):
"""Loads a module
Example: load mod"""
module = module.strip()
if "cogs." not in module: module = "cogs." + module
if not module in list_cogs():
await bot.say("That module doesn't exist.")
return
set_cog(module, True)
try:
mod_obj = importlib.import_module(module)
importlib.reload(mod_obj)
bot.load_extension(mod_obj.__name__)
except Exception as e:
await bot.say('{}: {}'.format(type(e).__name__, e))
else:
await bot.say("Module enabled.")
@bot.command()
@checks.is_owner()
async def unload(*, module : str):
"""Unloads a module
Example: unload mod"""
module = module.strip()
if "cogs." not in module: module = "cogs." + module
if not module in list_cogs():
await bot.say("That module doesn't exist.")
return
set_cog(module, False)
try:
bot.unload_extension(module)
except Exception as e:
await bot.say('{}: {}'.format(type(e).__name__, e))
else:
await bot.say("Module disabled.")
@bot.command(name="reload")
@checks.is_owner()
async def _reload(*, module : str):
"""Reloads a module
Example: reload mod"""
module = module.strip()
if "cogs." not in module: module = "cogs." + module
if not module in list_cogs():
await bot.say("That module doesn't exist.")
return
set_cog(module, True)
try:
bot.unload_extension(module)
mod_obj = importlib.import_module(module)
importlib.reload(mod_obj)
bot.load_extension(mod_obj.__name__)
except Exception as e:
await bot.say('\U0001f52b')
await bot.say('{}: {}'.format(type(e).__name__, e))
else:
await bot.say("Module reloaded.")
@bot.command(pass_context=True, hidden=True) # Modified function, originally made by Rapptz
@checks.is_owner()
async def debug(ctx, *, code : str):
"""Evaluates code"""
code = code.strip('` ')
python = '```py\n{}\n```'
result = None
try:
result = eval(code)
except Exception as e:
await bot.say(python.format(type(e).__name__ + ': ' + str(e)))
return
if asyncio.iscoroutine(result):
result = await result
result = python.format(result)
if not ctx.message.channel.is_private:
censor = (settings.email, settings.password)
r = "[EXPUNGED]"
for w in censor:
if w != "":
result = result.replace(w, r)
result = result.replace(w.lower(), r)
result = result.replace(w.upper(), r)
await bot.say(result)
@bot.group(name="set", pass_context=True)
async def _set(ctx):
"""Changes settings"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@_set.command(pass_context=True)
async def owner(ctx):
"""Sets owner"""
global lock
msg = ctx.message
if settings.owner != "id_here":
await bot.say("Owner ID has already been set.")
return
if lock:
await bot.say("A setowner request is already pending. Check the console.")
return
await bot.say("Confirm in the console that you're the owner.")
lock = True
t = threading.Thread(target=wait_for_answer, args=(ctx.message.author,))
t.start()
@_set.command()
@checks.is_owner()
async def prefix(*prefixes):
"""Sets prefixes
Must be separated by a space. Enclose in double
quotes if a prefix contains spaces."""
if prefixes == ():
await bot.say("Example: setprefix [ ! ^ .")
return
bot.command_prefix = sorted(prefixes, reverse=True)
settings.prefixes = sorted(prefixes, reverse=True)
if len(prefixes) > 1:
await bot.say("Prefixes set")
else:
await bot.say("Prefix set")
@_set.command(pass_context=True)
@checks.is_owner()
async def name(ctx, *name : str):
"""Sets Red's name"""
if name == ():
await send_cmd_help(ctx)
await bot.edit_profile(settings.password, username=" ".join(name))
await bot.say("Done.")
@_set.command(pass_context=True)
@checks.is_owner()
async def status(ctx, *status : str):
"""Sets Red's status"""
if status != ():
await bot.change_status(discord.Game(name=" ".join(status)))
else:
await bot.change_status(None)
await bot.say("Done.")
@_set.command()
@checks.is_owner()
async def avatar(url : str):
"""Sets Red's avatar"""
try:
async with aiohttp.get(url) as r:
data = await r.read()
await bot.edit_profile(settings.password, avatar=data)
await bot.say("Done.")
except:
await bot.say("Error.")
@_set.command(name="token")
@checks.is_owner()
async def _token(token : str):
"""Sets Red's login token"""
if len(token) < 50:
await bot.say("Invalid token.")
else:
settings.login_type = "token"
settings.email = token
settings.password = ""
await bot.say("Token set. Restart me.")
@bot.command()
@checks.is_owner()
async def shutdown():
"""Shuts down Red"""
await bot.logout()
@bot.command()
@checks.is_owner()
async def join(invite_url : discord.Invite):
"""Joins new server"""
if bot.user.bot == True:
msg = "I have a **BOT** tag, so I must be invited with an OAuth2 link:\n"
msg += "`https://discordapp.com/oauth2/authorize?&client_id=`__**`MY_CLIENT_ID_HERE`**__`&scope=bot`\n"
msg += "For more information: https://twentysix26.github.io/Red-Docs/red_guide_bot_accounts/#bot-invites"
await bot.say(msg)
else:
try:
await bot.accept_invite(invite_url)
await bot.say("Server joined.")
except discord.NotFound:
await bot.say("The invite was invalid or expired.")
except discord.HTTPException:
await bot.say("I wasn't able to accept the invite. Try again.")
@bot.command(pass_context=True)
@checks.is_owner()
async def leave(ctx):
"""Leaves server"""
message = ctx.message
await bot.say("Are you sure you want me to leave this server? Type yes to confirm")
response = await bot.wait_for_message(author=message.author)
if response.content.lower().strip() == "yes":
await bot.say("Alright. Bye :wave:")
await bot.leave_server(message.server)
else:
await bot.say("Ok I'll stay here then.")
@bot.command(name="uptime")
async def _uptime():
"""Shows Red's uptime"""
up = abs(bot.uptime - int(time.perf_counter()))
up = str(datetime.timedelta(seconds=up))
await bot.say("`Uptime: {}`".format(up))
@bot.command()
async def version():
"""Shows Red's current version"""
loop = asyncio.get_event_loop()
response = loop.run_in_executor(None, get_version)
result = await asyncio.wait_for(response, timeout=10)
await bot.say(result)
def get_version():
getversion = os.popen(r'git show -s HEAD --format="%cr|%s|%h"').read()
version = getversion.split('|')
return 'Last updated: ``{}``\nCommit: ``{}``\nHash: ``{}``'.format(*version)
def user_allowed(message): def user_allowed(message):
@ -323,10 +97,12 @@ def user_allowed(message):
return True return True
if not message.channel.is_private: if not message.channel.is_private:
server = message.server server = message.server
names = (settings.get_server_admin(server),settings.get_server_mod(server)) names = (settings.get_server_admin(
results = map(lambda name: discord.utils.get(author.roles,name=name),names) server), settings.get_server_mod(server))
results = map(
lambda name: discord.utils.get(author.roles, name=name), names)
for r in results: for r in results:
if r != None: if r is not None:
return True return True
if author.id in mod.blacklist_list: if author.id in mod.blacklist_list:
@ -346,29 +122,17 @@ def user_allowed(message):
else: else:
return True return True
def wait_for_answer(author):
global lock
print(author.name + " requested to be set as owner. If this is you, type 'yes'. Otherwise press enter.")
print("*DO NOT* set anyone else as owner.")
choice = "None"
while choice.lower() != "yes" and choice == "None":
choice = input("> ")
if choice == "yes":
settings.owner = author.id
print(author.name + " has been set as owner.")
lock = False
owner.hidden = True
else:
print("setowner request has been ignored.")
lock = False
def list_cogs(): async def get_oauth_url():
cogs = glob.glob("cogs/*.py") endpoint = "https://discordapp.com/api/oauth2/applications/@me"
clean = [] if bot.headers.get('authorization') is None:
for c in cogs: bot.headers['authorization'] = "Bot {}".format(settings.email)
c = c.replace("/", "\\") # Linux fix
clean.append("cogs." + c.split("\\")[1].replace(".py", "")) async with bot.session.get(endpoint, headers=bot.headers) as resp:
return clean data = await resp.json()
return discord.utils.oauth_url(data.get('id'))
def check_folders(): def check_folders():
folders = ("data", "data/red", "cogs", "cogs/utils") folders = ("data", "data/red", "cogs", "cogs/utils")
@ -377,18 +141,25 @@ def check_folders():
print("Creating " + folder + " folder...") print("Creating " + folder + " folder...")
os.makedirs(folder) os.makedirs(folder)
def check_configs(): def check_configs():
if settings.bot_settings == settings.default_settings: if settings.bot_settings == settings.default_settings:
print("Red - First run configuration\n") print("Red - First run configuration\n")
print("You either need a normal account or a bot account to use Red. *Do not* use your own.") print("You either need a normal account or a bot account to use Red. "
print("For more information on bot accounts see: https://discordapp.com/developers/docs/topics/oauth2#bot-vs-user-accounts") "*Do not* use your own.")
print("If you're not interested in a bot account, create a normal account on https://discordapp.com") print("For more information on bot accounts see: https://twentysix26."
print("Otherwise make one and copy the token from https://discordapp.com/developers/applications/me") "github.io/Red-Docs/red_guide_bot_accounts/"
"#creating-a-new-bot-account")
print("If you decide to use a normal account, create an account for "
"your bot on https://discordapp.com then enter your email here.")
print("Otherwise make a bot account and copy the token from "
"https://discordapp.com/developers/applications/me then enter "
"your token here.")
print("\nType your email or token:") print("\nType your email or token:")
choice = input("> ") choice = input("> ")
if "@" not in choice and len(choice) >= 50: #Assuming token if "@" not in choice and len(choice) >= 50: # Assuming token
settings.login_type = "token" settings.login_type = "token"
settings.email = choice settings.email = choice
elif "@" in choice: elif "@" in choice:
@ -397,10 +168,12 @@ def check_configs():
settings.password = input("\nPassword> ") settings.password = input("\nPassword> ")
else: else:
os.remove('data/red/settings.json') os.remove('data/red/settings.json')
input("Invalid input. Restart Red and repeat the configuration process.") input("Invalid input. Restart Red and repeat the configuration "
"process.")
exit(1) exit(1)
print("\nChoose a prefix (or multiple ones, one at once) for the commands. Type exit when you're done. Example prefix: !") print("\nChoose a prefix (or multiple ones, one at once) for the "
"commands. Type exit when you're done. Example prefix: !")
prefixes = [] prefixes = []
new_prefix = "" new_prefix = ""
while new_prefix.lower() != "exit" or prefixes == []: while new_prefix.lower() != "exit" or prefixes == []:
@ -410,24 +183,32 @@ def check_configs():
# Remember we're using property's here, oh well... # Remember we're using property's here, oh well...
settings.prefixes = sorted(prefixes, reverse=True) settings.prefixes = sorted(prefixes, reverse=True)
print("\nIf you know what an User ID is, input *your own* now and press enter.") print("\nIf you know what an User ID is, input *your own* now and"
print("Otherwise you can just set yourself as owner later with '[prefix]set owner'. Leave empty and press enter in this case.") " press enter.")
print("Otherwise you can just set yourself as owner later with "
"'[prefix]set owner'. Leave empty and press enter in this case.")
settings.owner = input("\nID> ") settings.owner = input("\nID> ")
if settings.owner == "": settings.owner = "id_here" if settings.owner == "":
settings.owner = "id_here"
if not settings.owner.isdigit() or len(settings.owner) < 17: if not settings.owner.isdigit() or len(settings.owner) < 17:
if settings.owner != "id_here": if settings.owner != "id_here":
print("\nERROR: What you entered is not a valid ID. Set yourself as owner later with [prefix]set owner") print("\nERROR: What you entered is not a valid ID. Set "
"yourself as owner later with [prefix]set owner")
settings.owner = "id_here" settings.owner = "id_here"
print("\nInput the admin role's name. Anyone with this role will be able to use the bot's admin commands") print("\nInput the admin role's name. Anyone with this role will be "
"able to use the bot's admin commands")
print("Leave blank for default name (Transistor)") print("Leave blank for default name (Transistor)")
settings.default_admin = input("\nAdmin role> ") settings.default_admin = input("\nAdmin role> ")
if settings.default_admin == "": settings.default_admin = "Transistor" if settings.default_admin == "":
settings.default_admin = "Transistor"
print("\nInput the moderator role's name. Anyone with this role will be able to use the bot's mod commands") print("\nInput the moderator role's name. Anyone with this role will "
"be able to use the bot's mod commands")
print("Leave blank for default name (Process)") print("Leave blank for default name (Process)")
settings.default_mod = input("\nModerator role> ") settings.default_mod = input("\nModerator role> ")
if settings.default_mod == "": settings.default_mod = "Process" if settings.default_mod == "":
settings.default_mod = "Process"
cogs_s_path = "data/red/cogs.json" cogs_s_path = "data/red/cogs.json"
cogs = {} cogs = {}
@ -436,20 +217,30 @@ def check_configs():
with open(cogs_s_path, "w") as f: with open(cogs_s_path, "w") as f:
f.write(json.dumps(cogs)) f.write(json.dumps(cogs))
def set_logger(): def set_logger():
global logger global logger
logger = logging.getLogger("discord") logger = logging.getLogger("discord")
logger.setLevel(logging.WARNING) logger.setLevel(logging.WARNING)
handler = logging.FileHandler(filename='data/red/discord.log', encoding='utf-8', mode='a') handler = logging.FileHandler(
handler.setFormatter(logging.Formatter('%(asctime)s %(module)s %(lineno)d %(message)s', datefmt="[%d/%m/%Y %H:%M]")) filename='data/red/discord.log', encoding='utf-8', mode='a')
handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: '
'%(message)s',
datefmt="[%d/%m/%Y %H:%M]"))
logger.addHandler(handler) logger.addHandler(handler)
logger = logging.getLogger("red") logger = logging.getLogger("red")
logger.setLevel(logging.WARNING) logger.setLevel(logging.WARNING)
handler = logging.FileHandler(filename='data/red/red.log', encoding='utf-8', mode='a') handler = logging.FileHandler(
handler.setFormatter(logging.Formatter('%(asctime)s %(module)s %(lineno)d %(message)s', datefmt="[%d/%m/%Y %H:%M]")) filename='data/red/red.log', encoding='utf-8', mode='a')
handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s %(module)s %(funcName)s %(lineno)d: '
'%(message)s',
datefmt="[%d/%m/%Y %H:%M]"))
logger.addHandler(handler) logger.addHandler(handler)
def get_answer(): def get_answer():
choices = ("yes", "y", "no", "n") choices = ("yes", "y", "no", "n")
c = "" c = ""
@ -460,6 +251,7 @@ def get_answer():
else: else:
return False return False
def set_cog(cog, value): def set_cog(cog, value):
with open('data/red/cogs.json', "r") as f: with open('data/red/cogs.json', "r") as f:
data = json.load(f) data = json.load(f)
@ -467,6 +259,7 @@ def set_cog(cog, value):
with open('data/red/cogs.json', "w") as f: with open('data/red/cogs.json', "w") as f:
f.write(json.dumps(data)) f.write(json.dumps(data))
def load_cogs(): def load_cogs():
try: try:
if sys.argv[1] == "--no-prompt": if sys.argv[1] == "--no-prompt":
@ -476,39 +269,47 @@ def load_cogs():
except: except:
no_prompt = False no_prompt = False
with open('data/red/cogs.json', "r") as f: try:
data = json.load(f) with open('data/red/cogs.json', "r") as f:
register = tuple(data.keys()) #known cogs registry = json.load(f)
extensions = list_cogs() except:
registry = {}
if extensions: print("\nLoading cogs...\n") bot.load_extension('cogs.owner')
owner_cog = bot.get_cog('Owner')
if owner_cog is None:
print("You got rid of the damn OWNER cog, it has special functions"
" that I require to run.\n\n"
"I can't start without it!")
print()
print("Go here to find a new copy:\n{}".format(
"https://github.com/Twentysix26/Red-DiscordBot"))
exit(1)
failed = [] failed = []
extensions = owner_cog._list_cogs()
for extension in extensions: for extension in extensions:
if extension in register: if extension.lower() == "cogs.owner":
if data[extension]: continue
try: in_reg = extension in registry
bot.load_extension(extension) if not (in_reg or no_prompt):
except Exception as e: print("\nNew extension: {}".format(extension))
print(e) print("Load it?(y/n)")
failed.append(extension) if not get_answer():
else: registry[extension] = False
if not no_prompt: continue
print("\nNew extension: " + extension) registry[extension] = True
print("Load it?(y/n)") try:
if get_answer(): owner_cog._load_cog(extension)
data[extension] = True except Exception as e:
try: print("{}: {}".format(e.__class__.__name__, str(e)))
bot.load_extension(extension) logger.exception(e)
except Exception as e: failed.append(extension)
print(e) registry[extension] = False
failed.append(extension)
else:
data[extension] = False
if extensions: if extensions:
with open('data/red/cogs.json', "w") as f: with open('data/red/cogs.json', "w") as f:
f.write(json.dumps(data)) f.write(json.dumps(registry))
if failed: if failed:
print("\nFailed to load: ", end="") print("\nFailed to load: ", end="")
@ -516,6 +317,9 @@ def load_cogs():
print(m + " ", end="") print(m + " ", end="")
print("\n") print("\n")
return owner_cog
def main(): def main():
global settings global settings
global checks global checks
@ -523,7 +327,7 @@ def main():
check_folders() check_folders()
check_configs() check_configs()
set_logger() set_logger()
load_cogs() owner_cog = load_cogs()
if settings.prefixes != []: if settings.prefixes != []:
bot.command_prefix = settings.prefixes bot.command_prefix = settings.prefixes
else: else:
@ -534,22 +338,24 @@ def main():
else: else:
print("Once you're owner use !set prefix to set it.") print("Once you're owner use !set prefix to set it.")
if settings.owner == "id_here": if settings.owner == "id_here":
print("Owner has not been set yet. Do '{}set owner' in chat to set yourself as owner.".format(bot.command_prefix[0])) print("Owner has not been set yet. Do '{}set owner' in chat to set "
"yourself as owner.".format(bot.command_prefix[0]))
else: else:
owner.hidden = True # Hides the set owner command from help owner_cog.owner.hidden = True # Hides the set owner command from help
print("-- Logging in.. --") print("-- Logging in.. --")
print("Make sure to keep your bot updated by using: git pull") print("Make sure to keep your bot updated by using: git pull")
print("and: pip3 install --upgrade git+https://github.com/Rapptz/discord.py@async") print("and: pip3 install --upgrade git+https://github.com/Rapptz/"
"discord.py@async")
if settings.login_type == "token": if settings.login_type == "token":
_token.hidden = True owner_cog._token.hidden = True
try: try:
yield from bot.login(settings.email) yield from bot.login(settings.email)
except TypeError as e: except TypeError as e:
print(e) print(e)
msg = "\n" msg = ("\nYou are using an outdated discord.py.\n"
msg += "You are using an outdated discord.py.\n" "update your discord.py with by running this in your cmd "
msg += "update your discord.py with by running this in your cmd prompt/terminal.\n" "prompt/terminal.\npip3 install --upgrade git+https://"
msg += "pip3 install --upgrade git+https://github.com/Rapptz/discord.py@async" "github.com/Rapptz/discord.py@async")
sys.exit(msg) sys.exit(msg)
else: else:
yield from bot.login(settings.email, settings.password) yield from bot.login(settings.email, settings.password)
@ -561,9 +367,12 @@ if __name__ == '__main__':
loop.run_until_complete(main()) loop.run_until_complete(main())
except discord.LoginFailure: except discord.LoginFailure:
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
print("Invalid login credentials. Restart Red and configure it properly.") print("Invalid login credentials. Restart Red and configure it"
shutil.copy('data/red/settings.json', 'data/red/settings-{}.bak'.format(int(time.time()))) " properly.")
os.remove('data/red/settings.json') # Hopefully this won't backfire in case of discord servers' problems shutil.copy('data/red/settings.json',
'data/red/settings-{}.bak'.format(int(time.time())))
# Hopefully this won't backfire in case of discord servers' problems
os.remove('data/red/settings.json')
except: except:
logger.error(traceback.format_exc()) logger.error(traceback.format_exc())
loop.run_until_complete(bot.logout()) loop.run_until_complete(bot.logout())