Red's launcher / Downloader reqs autoinstall (#552)

This commit is contained in:
Twentysix 2017-01-05 02:19:54 +01:00 committed by GitHub
parent 04b00b7726
commit c987a89e9c
9 changed files with 686 additions and 147 deletions

View File

@ -12,6 +12,7 @@ import discord
from functools import partial from functools import partial
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from time import time from time import time
from importlib.util import find_spec
NUM_THREADS = 4 NUM_THREADS = 4
REPO_NONEX = 0x1 REPO_NONEX = 0x1
@ -27,6 +28,10 @@ class CloningError(UpdateError):
pass pass
class RequirementFail(UpdateError):
pass
class Downloader: class Downloader:
"""Cog downloader/installer.""" """Cog downloader/installer."""
@ -207,6 +212,7 @@ class Downloader:
updated_cogs = [] updated_cogs = []
new_cogs = [] new_cogs = []
deleted_cogs = [] deleted_cogs = []
failed_cogs = []
error_repos = {} error_repos = {}
installed_updated_cogs = [] installed_updated_cogs = []
@ -233,6 +239,21 @@ class Downloader:
msg = await self._robust_edit(msg, base_msg + status) msg = await self._robust_edit(msg, base_msg + status)
status = 'done. ' 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 if not any(self.repos[repo][cog]['INSTALLED'] for
repo, cog, _ in updated_cogs): repo, cog, _ in updated_cogs):
status += ' No updates to apply. ' status += ' No updates to apply. '
@ -246,6 +267,10 @@ class Downloader:
if updated_cogs: if updated_cogs:
status += '\nUpdated cogs: ' \ status += '\nUpdated cogs: ' \
+ ', '.join('%s/%s' % c[:2] for c in updated_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: if error_repos:
status += '\nThe following repos failed to update: ' status += '\nThe following repos failed to update: '
for n, what in error_repos.items(): for n, what in error_repos.items():
@ -253,15 +278,6 @@ class Downloader:
msg = await self._robust_edit(msg, base_msg + status) 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: if not installed_updated_cogs:
return return
@ -280,9 +296,12 @@ class Downloader:
await self.bot.say("Ok then, you can reload cogs with" await self.bot.say("Ok then, you can reload cogs with"
" `{}reload <cog_name>`".format(ctx.prefix)) " `{}reload <cog_name>`".format(ctx.prefix))
elif answer.content.lower().strip() == "yes": elif answer.content.lower().strip() == "yes":
registry = dataIO.load_json("data/red/cogs.json")
update_list = [] update_list = []
fail_list = [] fail_list = []
for repo, cog, _ in installed_updated_cogs: for repo, cog, _ in installed_updated_cogs:
if not registry.get('cogs.' + cog, False):
continue
try: try:
self.bot.unload_extension("cogs." + cog) self.bot.unload_extension("cogs." + cog)
self.bot.load_extension("cogs." + cog) self.bot.load_extension("cogs." + cog)
@ -342,8 +361,14 @@ class Downloader:
if cog not in self.repos[repo_name]: if cog not in self.repos[repo_name]:
await self.bot.say("That cog isn't available from that repo.") await self.bot.say("That cog isn't available from that repo.")
return return
install_cog = await self.install(repo_name, cog)
data = self.get_info_data(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: if data is not None:
install_msg = data.get("INSTALL_MSG", None) install_msg = data.get("INSTALL_MSG", None)
if install_msg: if install_msg:
@ -368,13 +393,39 @@ class Downloader:
await self.bot.say("That cog doesn't exist. Use cog list to see" await self.bot.say("That cog doesn't exist. Use cog list to see"
" the full list.") " 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'): if cog.endswith('.py'):
cog = cog[:-3] cog = cog[:-3]
path = self.repos[repo_name][cog]['file'] path = self.repos[repo_name][cog]['file']
cog_folder_path = self.repos[repo_name][cog]['folder'] cog_folder_path = self.repos[repo_name][cog]['folder']
cog_data_path = os.path.join(cog_folder_path, 'data') 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") to_path = os.path.join("cogs/", cog + ".py")
@ -387,7 +438,10 @@ class Downloader:
os.path.join('data/', cog)) os.path.join('data/', cog))
self.repos[repo_name][cog]['INSTALLED'] = True self.repos[repo_name][cog]['INSTALLED'] = True
self.save_repos() self.save_repos()
return True if not reqs_failed:
return True
else:
raise RequirementFail()
def get_info_data(self, repo_name, cog=None): def get_info_data(self, repo_name, cog=None):
if cog is not None: if cog is not None:
@ -440,6 +494,9 @@ class Downloader:
git_name = splitted[-1] git_name = splitted[-1]
return git_name[:-4] return git_name[:-4]
def is_lib_installed(self, name):
return bool(find_spec(name))
def _do_first_run(self): def _do_first_run(self):
invalid = [] invalid = []
save = False save = False

View File

@ -490,9 +490,31 @@ class Owner:
@commands.command() @commands.command()
@checks.is_owner() @checks.is_owner()
async def shutdown(self): async def shutdown(self, silently : bool=False):
"""Shuts down Red""" """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) @commands.group(name="command", pass_context=True)
@checks.is_owner() @checks.is_owner()

View File

@ -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

536
launcher.py Normal file
View File

@ -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)

57
red.py
View File

@ -1,10 +1,12 @@
import asyncio import asyncio
import os import os
import sys import sys
sys.path.insert(0, "lib")
import logging import logging
import logging.handlers import logging.handlers
import traceback import traceback
import datetime import datetime
import subprocess
try: try:
assert sys.version_info >= (3, 5) assert sys.version_info >= (3, 5)
@ -64,6 +66,7 @@ class Bot(commands.Bot):
self._message_modifiers = [] self._message_modifiers = []
self.settings = Settings() self.settings = Settings()
self._intro_displayed = False self._intro_displayed = False
self._restart_requested = False
self.logger = set_logger(self) self.logger = set_logger(self)
if 'self_bot' in kwargs: if 'self_bot' in kwargs:
self.settings.self_bot = kwargs['self_bot'] self.settings.self_bot = kwargs['self_bot']
@ -91,6 +94,15 @@ class Bot(commands.Bot):
return await super().send_message(*args, **kwargs) 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): def add_message_modifier(self, func):
""" """
Adds a message modifier to the bot Adds a message modifier to the bot
@ -174,6 +186,40 @@ class Bot(commands.Bot):
else: else:
return True 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): class Formatter(commands.HelpFormatter):
def __init__(self, *args, **kwargs): 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") print("\nOfficial server: https://discord.me/Red-DiscordBot")
if os.name == "nt" and os.path.isfile("update.bat"): print("Make sure to keep your bot updated. Select the 'Update' "
print("\nMake sure to keep your bot updated by running the file " "option from the launcher.")
"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]")
await bot.get_cog('Owner').disable_commands() await bot.get_cog('Owner').disable_commands()
@ -580,3 +621,5 @@ if __name__ == '__main__':
loop.close() loop.close()
if error: if error:
exit(1) exit(1)
if bot._restart_requested:
exit(26)

View File

@ -0,0 +1,4 @@
pip
git+git://github.com/Rapptz/discord.py.git
youtube_dl
imgurpython

View File

@ -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

View File

@ -6,7 +6,7 @@ pushd %~dp0
::Attempts to start py launcher without relying on PATH ::Attempts to start py launcher without relying on PATH
%SYSTEMROOT%\py.exe --version > NUL 2>&1 %SYSTEMROOT%\py.exe --version > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO attempt IF %ERRORLEVEL% NEQ 0 GOTO attempt
%SYSTEMROOT%\py.exe -3 red.py %SYSTEMROOT%\py.exe -3 launcher.py
PAUSE PAUSE
GOTO end GOTO end
@ -14,7 +14,7 @@ GOTO end
:attempt :attempt
py.exe --version > NUL 2>&1 py.exe --version > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO lastattempt IF %ERRORLEVEL% NEQ 0 GOTO lastattempt
py.exe -3 red.py py.exe -3 launcher.py
PAUSE PAUSE
GOTO end GOTO end
@ -22,7 +22,7 @@ GOTO end
:lastattempt :lastattempt
python.exe --version > NUL 2>&1 python.exe --version > NUL 2>&1
IF %ERRORLEVEL% NEQ 0 GOTO message IF %ERRORLEVEL% NEQ 0 GOTO message
python.exe red.py python.exe launcher.py
PAUSE PAUSE
GOTO end GOTO end

View File

@ -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