From c987a89e9c8e1f445c0f1e9617c1e1fb2622ac9b Mon Sep 17 00:00:00 2001 From: Twentysix Date: Thu, 5 Jan 2017 02:19:54 +0100 Subject: [PATCH] Red's launcher / Downloader reqs autoinstall (#552) --- cogs/downloader.py | 81 ++++- cogs/owner.py | 26 +- download_ffmpeg.bat | 28 -- launcher.py | 536 +++++++++++++++++++++++++++++ red.py | 57 ++- requirements_no_audio.txt | 4 + startRedLoop.bat | 37 -- startRed.bat => start_launcher.bat | 6 +- update.bat | 58 ---- 9 files changed, 686 insertions(+), 147 deletions(-) delete mode 100644 download_ffmpeg.bat create mode 100644 launcher.py create mode 100644 requirements_no_audio.txt delete mode 100644 startRedLoop.bat rename startRed.bat => start_launcher.bat (90%) delete mode 100644 update.bat diff --git a/cogs/downloader.py b/cogs/downloader.py index 803d05a65..fdd0a80b9 100644 --- a/cogs/downloader.py +++ b/cogs/downloader.py @@ -12,6 +12,7 @@ import discord from functools import partial from concurrent.futures import ThreadPoolExecutor from time import time +from importlib.util import find_spec NUM_THREADS = 4 REPO_NONEX = 0x1 @@ -27,6 +28,10 @@ class CloningError(UpdateError): pass +class RequirementFail(UpdateError): + pass + + class Downloader: """Cog downloader/installer.""" @@ -207,6 +212,7 @@ class Downloader: updated_cogs = [] new_cogs = [] deleted_cogs = [] + failed_cogs = [] error_repos = {} installed_updated_cogs = [] @@ -233,6 +239,21 @@ class Downloader: msg = await self._robust_edit(msg, base_msg + status) status = 'done. ' + for t in updated_cogs: + repo, cog, _ = t + if self.repos[repo][cog]['INSTALLED']: + try: + await self.install(repo, cog, + no_install_on_reqs_fail=False) + except RequirementFail: + failed_cogs.append(t) + else: + installed_updated_cogs.append(t) + + for t in updated_cogs.copy(): + if t in failed_cogs: + updated_cogs.remove(t) + if not any(self.repos[repo][cog]['INSTALLED'] for repo, cog, _ in updated_cogs): status += ' No updates to apply. ' @@ -246,6 +267,10 @@ class Downloader: if updated_cogs: status += '\nUpdated cogs: ' \ + ', '.join('%s/%s' % c[:2] for c in updated_cogs) + '.' + if failed_cogs: + status += '\nCogs that got new requirements which have ' + \ + 'failed to install: ' + \ + ', '.join('%s/%s' % c[:2] for c in failed_cogs) + '.' if error_repos: status += '\nThe following repos failed to update: ' for n, what in error_repos.items(): @@ -253,15 +278,6 @@ class Downloader: msg = await self._robust_edit(msg, base_msg + status) - registry = dataIO.load_json("data/red/cogs.json") - - for t in updated_cogs: - repo, cog, _ = t - if (self.repos[repo][cog]['INSTALLED'] and - registry.get('cogs.' + cog, False)): - installed_updated_cogs.append(t) - await self.install(repo, cog) - if not installed_updated_cogs: return @@ -280,9 +296,12 @@ class Downloader: await self.bot.say("Ok then, you can reload cogs with" " `{}reload `".format(ctx.prefix)) elif answer.content.lower().strip() == "yes": + registry = dataIO.load_json("data/red/cogs.json") update_list = [] fail_list = [] for repo, cog, _ in installed_updated_cogs: + if not registry.get('cogs.' + cog, False): + continue try: self.bot.unload_extension("cogs." + cog) self.bot.load_extension("cogs." + cog) @@ -342,8 +361,14 @@ class Downloader: if cog not in self.repos[repo_name]: await self.bot.say("That cog isn't available from that repo.") return - install_cog = await self.install(repo_name, cog) data = self.get_info_data(repo_name, cog) + try: + install_cog = await self.install(repo_name, cog, notify_reqs=True) + except RequirementFail: + await self.bot.say("That cog has requirements that I could not " + "install. Check the console for more " + "informations.") + return if data is not None: install_msg = data.get("INSTALL_MSG", None) if install_msg: @@ -368,13 +393,39 @@ class Downloader: await self.bot.say("That cog doesn't exist. Use cog list to see" " the full list.") - async def install(self, repo_name, cog): + async def install(self, repo_name, cog, *, notify_reqs=False, + no_install_on_reqs_fail=True): + # 'no_install_on_reqs_fail' will make the cog get installed anyway + # on requirements installation fail. This is necessary because due to + # how 'cog update' works right now, the user would have no way to + # reupdate the cog if the update fails, since 'cog update' only + # updates the cogs that get a new commit. + # This is not a great way to deal with the problem and a cog update + # rework would probably be the best course of action. + reqs_failed = False if cog.endswith('.py'): cog = cog[:-3] path = self.repos[repo_name][cog]['file'] cog_folder_path = self.repos[repo_name][cog]['folder'] cog_data_path = os.path.join(cog_folder_path, 'data') + data = self.get_info_data(repo_name, cog) + requirements = data.get("REQUIREMENTS", []) + + requirements = [r for r in requirements + if not self.is_lib_installed(r)] + + if requirements and notify_reqs: + await self.bot.say("Installing cog's requirements...") + + for requirement in requirements: + if not self.is_lib_installed(requirement): + success = await self.bot.pip_install(requirement) + if not success: + if no_install_on_reqs_fail: + raise RequirementFail() + else: + reqs_failed = True to_path = os.path.join("cogs/", cog + ".py") @@ -387,7 +438,10 @@ class Downloader: os.path.join('data/', cog)) self.repos[repo_name][cog]['INSTALLED'] = True self.save_repos() - return True + if not reqs_failed: + return True + else: + raise RequirementFail() def get_info_data(self, repo_name, cog=None): if cog is not None: @@ -440,6 +494,9 @@ class Downloader: git_name = splitted[-1] return git_name[:-4] + def is_lib_installed(self, name): + return bool(find_spec(name)) + def _do_first_run(self): invalid = [] save = False diff --git a/cogs/owner.py b/cogs/owner.py index f1ca9adf9..d2b596ffa 100644 --- a/cogs/owner.py +++ b/cogs/owner.py @@ -490,9 +490,31 @@ class Owner: @commands.command() @checks.is_owner() - async def shutdown(self): + async def shutdown(self, silently : bool=False): """Shuts down Red""" - await self.bot.logout() + wave = "\N{WAVING HAND SIGN}" + skin = "\N{EMOJI MODIFIER FITZPATRICK TYPE-3}" + try: # We don't want missing perms to stop our shutdown + if not silently: + await self.bot.say("Shutting down... " + wave + skin) + except: + pass + await self.bot.shutdown() + + @commands.command() + @checks.is_owner() + async def restart(self, silently : bool=False): + """Attempts to restart Red + + Makes Red quit with exit code 26 + The restart is not guaranteed: it must be dealt + with by the process manager in use""" + try: + if not silently: + await self.bot.say("Restarting...") + except: + pass + await self.bot.shutdown(restart=True) @commands.group(name="command", pass_context=True) @checks.is_owner() diff --git a/download_ffmpeg.bat b/download_ffmpeg.bat deleted file mode 100644 index 604586657..000000000 --- a/download_ffmpeg.bat +++ /dev/null @@ -1,28 +0,0 @@ -@echo off -pushd %~dp0 -IF "%PROCESSOR_ARCHITECTURE%"=="x86" (GOTO 32bit) else (GOTO 64bit) -echo Couldn't detect system bitness. -PAUSE -GOTO end - -:32bit -echo Windows 32bit detected. You'll need to manually install ffmpeg. Once you press enter I'm going to open a web page. -echo Keep following the instructions. -PAUSE -start "" https://ffmpeg.zeranoe.com/builds/ -echo Download "FFmpeg 32-bit Static" -echo Open the file and copy the 3 exe files from the "bin" folder into the Red-DiscordBot folder -PAUSE -GOTO end - -:64bit -echo Downloading files... Do not close. -echo Downloading ffmpeg.exe (1/3)... -powershell -Command "(New-Object Net.WebClient).DownloadFile('https://github.com/Twentysix26/Red-DiscordBot/raw/master/ffmpeg.exe', 'ffmpeg.exe')" -echo Downloading ffplay.exe (2/3)... -powershell -Command "(New-Object Net.WebClient).DownloadFile('https://github.com/Twentysix26/Red-DiscordBot/raw/master/ffplay.exe', 'ffplay.exe')" -echo Downloading ffprobe.exe (3/3)... -powershell -Command "(New-Object Net.WebClient).DownloadFile('https://github.com/Twentysix26/Red-DiscordBot/raw/master/ffprobe.exe', 'ffprobe.exe')" -PAUSE - -:end \ No newline at end of file diff --git a/launcher.py b/launcher.py new file mode 100644 index 000000000..70d002fb1 --- /dev/null +++ b/launcher.py @@ -0,0 +1,536 @@ +from __future__ import print_function +import os +import sys +import subprocess +try: # Older Pythons lack this + import urllib.request # We'll let them reach the Python +except ImportError: # check anyway + pass +import platform +import webbrowser +import hashlib +import argparse +import shutil +import stat +import time +try: + import pip +except ImportError: + pip = None + +REQS_DIR = "lib" +sys.path.insert(0, REQS_DIR) +REQS_TXT = "requirements.txt" +REQS_NO_AUDIO_TXT = "requirements_no_audio.txt" +FFMPEG_BUILDS_URL = "https://ffmpeg.zeranoe.com/builds/" + +INTRO = ("==========================\n" + "Red Discord Bot - Launcher\n" + "==========================\n") + +IS_WINDOWS = os.name == "nt" +IS_MAC = sys.platform == "darwin" +IS_64BIT = platform.machine().endswith("64") +INTERACTIVE_MODE = not len(sys.argv) > 1 # CLI flags = non-interactive +PYTHON_OK = sys.version_info >= (3, 5) + +FFMPEG_FILES = { + "ffmpeg.exe" : "e0d60f7c0d27ad9d7472ddf13e78dc89", + "ffplay.exe" : "d100abe8281cbcc3e6aebe550c675e09", + "ffprobe.exe" : "0e84b782c0346a98434ed476e937764f" +} + + +def parse_cli_arguments(): + parser = argparse.ArgumentParser(description="Red - Discord Bot's launcher") + parser.add_argument("--start", "-s", + help="Starts Red", + action="store_true") + parser.add_argument("--auto-restart", + help="Autorestarts Red in case of issues", + action="store_true") + parser.add_argument("--update-red", + help="Updates Red (git)", + action="store_true") + parser.add_argument("--update-reqs", + help="Updates requirements (w/ audio)", + action="store_true") + parser.add_argument("--update-reqs-no-audio", + help="Updates requirements (w/o audio)", + action="store_true") + parser.add_argument("--repair", + help="Issues a git reset --hard", + action="store_true") + return parser.parse_args() + + +def install_reqs(audio): + interpreter = sys.executable + + if interpreter is None: + print("Python interpreter not found.") + return + + txt = REQS_TXT if audio else REQS_NO_AUDIO_TXT + + args = [ + interpreter, "-m", + "pip", "install", + "--upgrade", + "--target", REQS_DIR, + "-r", txt + ] + + if IS_MAC: # --target is a problem on Homebrew. See PR #552 + args.remove("--target") + args.remove(REQS_DIR) + + code = subprocess.call(args) + + if code == 0: + print("\nRequirements setup completed.") + else: + print("\nAn error occured and the requirements setup might " + "not be completed. Consult the docs.\n") + + +def update_pip(): + interpreter = sys.executable + + if interpreter is None: + print("Python interpreter not found.") + return + + args = [ + interpreter, "-m", + "pip", "install", + "--upgrade", "pip" + ] + + code = subprocess.call(args) + + if code == 0: + print("\nPip has been updated.") + else: + print("\nAn error occurred and pip might not have been updated.") + + +def update_red(): + try: + code = subprocess.call(("git", "pull", "--ff-only")) + except FileNotFoundError: + print("\nError: Git not found. It's either not installed or not in " + "the PATH environment variable like requested in the guide.") + return + if code == 0: + print("\nRed has been updated") + else: + print("\nRed could not update properly. If this is caused by edits " + "you have made to the code you can try the repair option from " + "the Maintenance submenu") + + +def reset_red(reqs=False, data=False, cogs=False, git_reset=False): + if reqs: + try: + shutil.rmtree(REQS_DIR, onerror=remove_readonly) + print("Installed local packages have been wiped.") + except FileNotFoundError: + pass + except Exception as e: + print("An error occured when trying to remove installed " + "requirements: {}".format(e)) + if data: + try: + shutil.rmtree("data", onerror=remove_readonly) + print("'data' folder has been wiped.") + except FileNotFoundError: + pass + except Exception as e: + print("An error occured when trying to remove the 'data' folder: " + "{}".format(e)) + + if cogs: + try: + shutil.rmtree("cogs", onerror=remove_readonly) + print("'cogs' folder has been wiped.") + except FileNotFoundError: + pass + except Exception as e: + print("An error occured when trying to remove the 'cogs' folder: " + "{}".format(e)) + + if git_reset: + code = subprocess.call(("git", "reset", "--hard")) + if code == 0: + print("Red has been restored to the last local commit.") + else: + print("The repair has failed.") + + +def download_ffmpeg(bitness): + clear_screen() + repo = "https://github.com/Twentysix26/Red-DiscordBot/raw/master/" + verified = [] + + if bitness == "32bit": + print("Please download 'ffmpeg 32bit static' from the page that " + "is about to open.\nOnce done, open the 'bin' folder located " + "inside the zip.\nThere should be 3 files: ffmpeg.exe, " + "ffplay.exe, ffprobe.exe.\nPut all three of them into the " + "bot's main folder.") + time.sleep(4) + webbrowser.open(FFMPEG_BUILDS_URL) + return + + for filename in FFMPEG_FILES: + if os.path.isfile(filename): + print("{} already present. Verifying integrity... " + "".format(filename), end="") + _hash = calculate_md5(filename) + if _hash == FFMPEG_FILES[filename]: + verified.append(filename) + print("Ok") + continue + else: + print("Hash mismatch. Redownloading.") + print("Downloading {}... Please wait.".format(filename)) + with urllib.request.urlopen(repo + filename) as data: + with open(filename, "wb") as f: + f.write(data.read()) + print("Download completed.") + + for filename, _hash in FFMPEG_FILES.items(): + if filename in verified: + continue + print("Verifying {}... ".format(filename), end="") + if not calculate_md5(filename) != _hash: + print("Passed.") + else: + print("Hash mismatch. Please redownload.") + + print("\nAll files have been downloaded.") + + +def verify_requirements(): + try: + from discord.ext import commands + except ImportError: + return False + else: + return True + + +def is_dpy_audio_installed(): + """Detects if the audio portion of discord.py is installed""" + if not verify_requirements: + return None + try: + import nacl.secret + except ImportError: + return False + else: + return True + + +def requirements_menu(): + clear_screen() + while True: + print(INTRO) + print("Main requirements:\n") + print("1. Install basic + audio requirements (recommended)") + print("2. Install basic requirements") + if IS_WINDOWS: + print("\nffmpeg (audio requirement):") + print("3. Install ffmpeg 32bit") + if IS_64BIT: + print("4. Install ffmpeg 64bit (recommended on Windows 64bit)") + print("\n0. Go back") + choice = user_choice() + if choice == "1": + install_reqs(audio=True) + wait() + elif choice == "2": + install_reqs(audio=False) + wait() + elif choice == "3" and IS_WINDOWS: + download_ffmpeg(bitness="32bit") + wait() + elif choice == "4" and (IS_WINDOWS and IS_64BIT): + download_ffmpeg(bitness="64bit") + wait() + elif choice == "0": + break + clear_screen() + + +def update_menu(): + clear_screen() + while True: + print(INTRO) + print("Update:\n") + print("Red:") + print("1. Update Red + requirements (recommended)") + print("2. Update Red") + print("3. Update requirements") + print("\nOthers:") + print("4. Update pip (might require admin privileges)") + print("\n0. Go back") + choice = user_choice() + if choice == "1": + update_red() + print("Updating requirements...") + audio = is_dpy_audio_installed() + if audio is not None: + install_reqs(audio=audio) + else: + print("The requirements haven't been installed yet.") + wait() + elif choice == "2": + update_red() + wait() + elif choice == "3": + audio = is_dpy_audio_installed() + if audio is not None: + install_reqs(audio=audio) + else: + print("The requirements haven't been installed yet.") + wait() + elif choice == "4": + update_pip() + wait() + elif choice == "0": + break + clear_screen() + + +def maintenance_menu(): + clear_screen() + while True: + print(INTRO) + print("Maintenance:\n") + print("1. Repair Red (discards code changes, keeps data intact)") + print("2. Wipe 'data' folder (all settings, cogs' data...)") + print("3. Wipe 'lib' folder (all local requirements / local installed" + " python packages)") + print("4. Factory reset") + print("\n0. Go back") + choice = user_choice() + if choice == "1": + print("Any code modification you have made will be lost. Data/" + "non-default cogs will be left intact. Are you sure?") + if user_pick_yes_no(): + reset_red(git_reset=True) + wait() + elif choice == "2": + print("Are you sure? This will wipe the 'data' folder, which " + "contains all your settings and cogs' data.\nThe 'cogs' " + "folder, however, will be left intact.") + if user_pick_yes_no(): + reset_red(data=True) + wait() + elif choice == "3": + reset_red(reqs=True) + wait() + elif choice == "4": + print("Are you sure? This will wipe ALL your Red's installation " + "data.\nYou'll lose all your settings, cogs and any " + "modification you have made.\nThere is no going back.") + if user_pick_yes_no(): + reset_red(reqs=True, data=True, cogs=True, git_reset=True) + wait() + elif choice == "0": + break + clear_screen() + + +def run_red(autorestart): + interpreter = sys.executable + + if interpreter is None: # This should never happen + raise RuntimeError("Couldn't find Python's interpreter") + + if not verify_requirements(): + print("You don't have the requirements to start Red. " + "Install them from the launcher.") + if not INTERACTIVE_MODE: + exit(1) + + cmd = (interpreter, "red.py") + + while True: + try: + code = subprocess.call(cmd) + except KeyboardInterrupt: + code = 0 + break + else: + if code == 0: + break + elif code == 26: + print("Restarting Red...") + continue + else: + if not autorestart: + break + + print("Red has been terminated. Exit code: %d" % code) + + if INTERACTIVE_MODE: + wait() + + +def clear_screen(): + if IS_WINDOWS: + os.system("cls") + else: + os.system("clear") + + +def wait(): + if INTERACTIVE_MODE: + input("Press enter to continue.") + + +def user_choice(): + return input("> ").lower().strip() + + +def user_pick_yes_no(): + choice = None + yes = ("yes", "y") + no = ("no", "n") + while choice not in yes and choice not in no: + choice = input("Yes/No > ").lower().strip() + return choice in yes + + +def remove_readonly(func, path, excinfo): + os.chmod(path, stat.S_IWRITE) + func(path) + + +def calculate_md5(filename): + hash_md5 = hashlib.md5() + with open(filename, "rb") as f: + for chunk in iter(lambda: f.read(4096), b""): + hash_md5.update(chunk) + return hash_md5.hexdigest() + + +def create_fast_start_scripts(): + """Creates scripts for fast boot of Red without going + through the launcher""" + interpreter = sys.executable + if not interpreter: + return + + call = "\"{}\" launcher.py".format(interpreter) + start_red = "{} --start".format(call) + start_red_autorestart = "{} --start --auto-restart".format(call) + modified = False + + if IS_WINDOWS: + pause = "\npause" + ext = ".bat" + else: + pause = "\nread -rsp $'Press enter to continue...\n'" + if not IS_MAC: + ext = ".sh" + else: + ext = ".command" + + start_red = start_red + pause + start_red_autorestart = start_red_autorestart + pause + + files = { + "start_red" + ext : start_red, + "start_red_autorestart" + ext : start_red_autorestart + } + + for filename, content in files.items(): + if not os.path.isfile(filename): + print("Creating {}... (fast start scripts)".format(filename)) + modified = True + with open(filename, "w") as f: + f.write(content) + + if not IS_WINDOWS and modified: # Let's make them executable on Unix + for script in ("start_red.sh", "start_red_autorestart.sh"): + st = os.stat(script) + os.chmod(script, st.st_mode | stat.S_IEXEC) + + +def main(): + if IS_WINDOWS: + os.system("TITLE Red Discord Bot - Launcher") + clear_screen() + + try: + create_fast_start_scripts() + except Exception as e: + print("Failed making fast start scripts: {}\n".format(e)) + + while True: + print(INTRO) + + if not os.path.isdir(".git"): + print("WARNING: It doesnt' look like Red has been " + "installed with git.\nThis means that you won't " + "be able to update and some features won't be working.\n" + "A reinstallation is recommended. Follow the guide " + "properly this time:\n" + "https://twentysix26.github.io/Red-Docs/\n") + + print("1. Run Red /w autorestart in case of issues") + print("2. Run Red") + print("3. Update") + print("4. Install requirements") + print("5. Maintenance (repair, reset...)") + print("\n0. Quit") + choice = user_choice() + if choice == "1": + run_red(autorestart=True) + elif choice == "2": + run_red(autorestart=False) + elif choice == "3": + update_menu() + elif choice == "4": + requirements_menu() + elif choice == "5": + maintenance_menu() + elif choice == "0": + break + clear_screen() + +args = parse_cli_arguments() + +if __name__ == '__main__': + abspath = os.path.abspath(__file__) + dirname = os.path.dirname(abspath) + # Sets current directory to the script's + os.chdir(dirname) + if not PYTHON_OK: + print("Red needs Python 3.5 or superior. Install the required " + "version.\nPress enter to continue.") + if INTERACTIVE_MODE: + wait() + exit(1) + if pip is None: + print("Red cannot work without the pip module. Please make sure to " + "install Python without unchecking any option during the setup") + wait() + exit(1) + if args.repair: + reset_red(git_reset=True) + if args.update_red: + update_red() + if args.update_reqs: + install_reqs(audio=True) + elif args.update_reqs_no_audio: + install_reqs(audio=False) + if INTERACTIVE_MODE: + main() + elif args.start: + print("Starting Red...") + run_red(autorestart=args.auto_restart) diff --git a/red.py b/red.py index 5235786e5..75e454056 100644 --- a/red.py +++ b/red.py @@ -1,10 +1,12 @@ import asyncio import os import sys +sys.path.insert(0, "lib") import logging import logging.handlers import traceback import datetime +import subprocess try: assert sys.version_info >= (3, 5) @@ -64,6 +66,7 @@ class Bot(commands.Bot): self._message_modifiers = [] self.settings = Settings() self._intro_displayed = False + self._restart_requested = False self.logger = set_logger(self) if 'self_bot' in kwargs: self.settings.self_bot = kwargs['self_bot'] @@ -91,6 +94,15 @@ class Bot(commands.Bot): return await super().send_message(*args, **kwargs) + async def shutdown(self, *, restart=False): + """Gracefully quits Red with exit code 0 + + If restart is True, the exit code will be 26 instead + The launcher automatically restarts Red when that happens""" + if restart: + self._restart_requested = True + await self.logout() + def add_message_modifier(self, func): """ Adds a message modifier to the bot @@ -174,6 +186,40 @@ class Bot(commands.Bot): else: return True + async def pip_install(self, name, *, timeout=None): + """ + Installs a pip package in the local 'lib' folder in a thread safe + way. On Mac systems the 'lib' folder is not used. + Can specify the max seconds to wait for the task to complete + + Returns a bool indicating if the installation was successful + """ + + IS_MAC = sys.platform == "darwin" + interpreter = sys.executable + + if interpreter is None: + raise RuntimeError("Couldn't find Python's interpreter") + + args = [ + interpreter, "-m", + "pip", "install", + "--upgrade", + "--target", "lib", + name + ] + + if IS_MAC: # --target is a problem on Homebrew. See PR #552 + args.remove("--target") + args.remove("lib") + + def install(): + code = subprocess.call(args) + return not bool(code) + + response = self.loop.run_in_executor(None, install) + return await asyncio.wait_for(response, timeout=timeout) + class Formatter(commands.HelpFormatter): def __init__(self, *args, **kwargs): @@ -282,13 +328,8 @@ def initialize(bot_class=Bot, formatter_class=Formatter): print("\nOfficial server: https://discord.me/Red-DiscordBot") - if os.name == "nt" and os.path.isfile("update.bat"): - print("\nMake sure to keep your bot updated by running the file " - "update.bat") - else: - print("\nMake sure to keep your bot updated by using: git pull") - print("and: pip3 install -U git+https://github.com/Rapptz/" - "discord.py@master#egg=discord.py[voice]") + print("Make sure to keep your bot updated. Select the 'Update' " + "option from the launcher.") await bot.get_cog('Owner').disable_commands() @@ -580,3 +621,5 @@ if __name__ == '__main__': loop.close() if error: exit(1) + if bot._restart_requested: + exit(26) diff --git a/requirements_no_audio.txt b/requirements_no_audio.txt new file mode 100644 index 000000000..9e81a94a5 --- /dev/null +++ b/requirements_no_audio.txt @@ -0,0 +1,4 @@ +pip +git+git://github.com/Rapptz/discord.py.git +youtube_dl +imgurpython \ No newline at end of file diff --git a/startRedLoop.bat b/startRedLoop.bat deleted file mode 100644 index 7102897db..000000000 --- a/startRedLoop.bat +++ /dev/null @@ -1,37 +0,0 @@ -@echo off -chcp 65001 -echo. -pushd %~dp0 - -:loopstart - -::Attempts to start py launcher without relying on PATH -%SYSTEMROOT%\py.exe --version > NUL 2>&1 -IF %ERRORLEVEL% NEQ 0 GOTO attempt -%SYSTEMROOT%\py.exe -3 red.py -timeout 3 -GOTO loopstart - -::Attempts to start py launcher by relying on PATH -:attempt -py.exe --version > NUL 2>&1 -IF %ERRORLEVEL% NEQ 0 GOTO lastattempt -py.exe -3 red.py -timeout 3 -GOTO loopstart - -::As a last resort, attempts to start whatever Python there is -:lastattempt -python.exe --version > NUL 2>&1 -IF %ERRORLEVEL% NEQ 0 GOTO message -python.exe red.py -timeout 3 -GOTO loopstart - -:message -echo Couldn't find a valid Python ^>3.5 installation. Python needs to be installed and available in the PATH environment -echo variable. -echo https://twentysix26.github.io/Red-Docs/red_win_requirements/#software -PAUSE - -:end diff --git a/startRed.bat b/start_launcher.bat similarity index 90% rename from startRed.bat rename to start_launcher.bat index 8d0b50a7a..69bf3d81e 100644 --- a/startRed.bat +++ b/start_launcher.bat @@ -6,7 +6,7 @@ pushd %~dp0 ::Attempts to start py launcher without relying on PATH %SYSTEMROOT%\py.exe --version > NUL 2>&1 IF %ERRORLEVEL% NEQ 0 GOTO attempt -%SYSTEMROOT%\py.exe -3 red.py +%SYSTEMROOT%\py.exe -3 launcher.py PAUSE GOTO end @@ -14,7 +14,7 @@ GOTO end :attempt py.exe --version > NUL 2>&1 IF %ERRORLEVEL% NEQ 0 GOTO lastattempt -py.exe -3 red.py +py.exe -3 launcher.py PAUSE GOTO end @@ -22,7 +22,7 @@ GOTO end :lastattempt python.exe --version > NUL 2>&1 IF %ERRORLEVEL% NEQ 0 GOTO message -python.exe red.py +python.exe launcher.py PAUSE GOTO end diff --git a/update.bat b/update.bat deleted file mode 100644 index 678a75402..000000000 --- a/update.bat +++ /dev/null @@ -1,58 +0,0 @@ -@echo off -chcp 65001 -echo. -pushd %~dp0 - -net session >nul 2>&1 -if NOT %errorLevel% == 0 ( - echo This script NEEDS to be run as administrator. - echo Right click on it ^-^> Run as administrator - echo. - PAUSE - GOTO end -) - -::Checking git and updating -git.exe --version > NUL 2>&1 -IF %ERRORLEVEL% NEQ 0 GOTO gitmessage -echo Updating Red... -git stash -git pull - -echo. -echo Updating requirements... -::Attempts to start py launcher without relying on PATH -%SYSTEMROOT%\py.exe --version > NUL 2>&1 -IF %ERRORLEVEL% NEQ 0 GOTO attempt -%SYSTEMROOT%\py.exe -3 -m pip install --upgrade -r requirements.txt -PAUSE -GOTO end - -::Attempts to start py launcher by relying on PATH -:attempt -py.exe --version > NUL 2>&1 -IF %ERRORLEVEL% NEQ 0 GOTO lastattempt -py.exe -3 -m pip install --upgrade -r requirements.txt -PAUSE -GOTO end - -::As a last resort, attempts to start whatever Python there is -:lastattempt -python.exe --version > NUL 2>&1 -IF %ERRORLEVEL% NEQ 0 GOTO pythonmessage -python.exe -m pip install --upgrade -r requirements.txt -PAUSE -GOTO end - -:pythonmessage -echo Couldn't find a valid Python ^>3.5 installation. Python needs to be installed and available in the PATH environment variable. -echo https://twentysix26.github.io/Red-Docs/red_install_windows/#software -PAUSE -GOTO end - -:gitmessage -echo Git is either not installed or not in the PATH environment variable. Install it again and add it to PATH like shown in the picture -echo https://twentysix26.github.io/Red-Docs/red_install_windows/#software -PAUSE - -:end