KiTTY, un bot Discord qui est un petit chat :) Il est basé sur une ancienne version du bot Red, sous Python 3.6 et qui a des fonctionnalités bien sympatiques !
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.
 

495 lines
15 KiB

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
from importlib.util import find_spec # check anyway
except ImportError:
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"
"KiTTY Launcher - Installer\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="KiTTY launcher")
parser.add_argument("--start", "-s",
help="Starts KiTTY",
action="store_true")
parser.add_argument("--auto-restart",
help="Autorestarts Red in case of issues",
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):
remove_reqs_readonly()
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 occurred 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 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 occurred 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 occurred 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 occurred 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():
sys.path_importer_cache = {} # I don't know if the cache reset has any
basic = find_spec("discord") # side effect. Without it, the lib folder
audio = find_spec("nacl") # wouldn't be seen if it didn't exist
if not basic: # when the launcher was started
return None
elif not audio:
return False
else:
return True
def is_git_installed():
try:
subprocess.call(["git", "--version"], stdout=subprocess.DEVNULL,
stdin =subprocess.DEVNULL,
stderr=subprocess.DEVNULL)
except FileNotFoundError:
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 (required for audio):")
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 maintenance_menu():
clear_screen()
while True:
print(INTRO)
print("Maintenance:\n")
print("1. Repair KiTTY (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 verify_requirements() is None:
print("You don't have the requirements to start Red. "
"Install them from the launcher.")
if not INTERACTIVE_MODE:
exit(1)
cmd = (interpreter, "kitty.py")
while True:
try:
code = subprocess.call(cmd)
except KeyboardInterrupt:
code = 0
break
else:
if code == 0:
break
elif code == 26:
print("Restarting KiTTY...")
continue
else:
if not autorestart:
break
print("KiTTY 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, 0o755)
func(path)
def remove_reqs_readonly():
"""Workaround for issue #569"""
if not os.path.isdir(REQS_DIR):
return
os.chmod(REQS_DIR, 0o755)
for root, dirs, files in os.walk(REQS_DIR):
for d in dirs:
os.chmod(os.path.join(root, d), 0o755)
for f in files:
os.chmod(os.path.join(root, f), 0o755)
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:
ccd = "pushd %~dp0\n"
pause = "\npause"
ext = ".bat"
else:
ccd = 'cd "$(dirname "$0")"\n'
pause = "\nread -rsp $'Press enter to continue...\\n'"
if not IS_MAC:
ext = ".sh"
else:
ext = ".command"
start_red = ccd + start_red + pause
start_red_autorestart = ccd + start_red_autorestart + pause
files = {
"start_red" + ext : start_red,
"start_red_autorestart" + ext : start_red_autorestart
}
if not IS_WINDOWS:
files["start_launcher" + ext] = ccd + call
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 files:
st = os.stat(script)
os.chmod(script, st.st_mode | stat.S_IEXEC)
def main():
print("Verifying git installation...")
has_git = is_git_installed()
is_git_installation = os.path.isdir(".git")
if IS_WINDOWS:
os.system("TITLE KiTTY 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 has_git:
print("WARNING: Git not found. This means that it's either not "
"installed or not in the PATH environment variable like "
"requested in the guide.\n")
print("1. Run KiTTY /w autorestart in case of issues")
print("2. Run KiTTY")
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":
print('zuck this')
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("KiTTY 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_reqs:
install_reqs(audio=True)
elif args.update_reqs_no_audio:
install_reqs(audio=False)
if INTERACTIVE_MODE:
main()
elif args.start:
print("Starting KiTTY...")
run_red(autorestart=args.auto_restart)