-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8f59d48
commit ae6d3f2
Showing
8 changed files
with
523 additions
and
38 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
import os | ||
import sys | ||
import shutil | ||
import json | ||
import time | ||
|
||
reinstall = '--reinstall' in sys.argv | ||
|
||
install_options = [ | ||
{ | ||
'id': 'stable', | ||
'name': '\U0001F48E Standard', | ||
'description': 'Uses the latest stable Nextcord version.', | ||
'default': True, | ||
'prefix': '', | ||
'color': '\x1b[32' | ||
} | ||
] | ||
|
||
if os.getcwd().endswith('/boot'): | ||
print('\x1b[31;1mYou are running the bootloader directly. Please run the run.sh file instead.\x1b[0m') | ||
sys.exit(1) | ||
|
||
with open('boot/internal.json') as file: | ||
internal = json.load(file) | ||
|
||
|
||
boot_config = {} | ||
try: | ||
with open('boot_config.json') as file: | ||
boot_config = json.load(file) | ||
except: | ||
if os.path.exists('update'): | ||
shutil.copy2('update/boot_config.json', 'boot_config.json') | ||
with open('boot_config.json') as file: | ||
boot_config = json.load(file) | ||
|
||
bootloader_config = boot_config.get('bootloader', {}) | ||
|
||
binary = bootloader_config.get('binary', 'py -3' if sys.platform == 'win32' else 'python3') | ||
options = bootloader_config.get('options') | ||
boot_file = bootloader_config.get('boot_file', internal["base_bootfile"]) | ||
autoreboot = bootloader_config.get('autoreboot', False) | ||
threshold = bootloader_config.get('autoreboot_threshold', 60) | ||
|
||
if not options: | ||
options = '' | ||
else: | ||
options = ' ' + ' '.join(options) | ||
|
||
if not '.install.json' in os.listdir() or reinstall: | ||
if os.path.isdir('update') and not reinstall: | ||
# unifier was likely updated from v2 or older | ||
print('\x1b[33;1mLegacy installation detected, skipping installer.\x1b[0m') | ||
with open('.install.json', 'w+') as file: | ||
json.dump( | ||
{ | ||
'product': internal["product"], | ||
'setup': False, | ||
'option': 'optimized' | ||
}, | ||
file | ||
) | ||
else: | ||
# this installation is fresh | ||
if not reinstall: | ||
print('\x1b[33;1mInstallation not detected, running installer...\x1b[0m') | ||
|
||
if len(install_options) == 1: | ||
install_option = install_options[0]['id'] | ||
else: | ||
print(f'\x1b[33;1mYou have {len(install_options)} install options available.\x1b[0m\n') | ||
|
||
for index in range(len(install_options)): | ||
option = install_options[index] | ||
print(f'{option["color"]};1m{option["name"]} (option {index})\x1b[0m') | ||
print(f'{option["color"]}m{option["description"]}\x1b[0m') | ||
|
||
print(f'\n\x1b[33;1mWhich installation option would you like to install? (0-{len(install_options)-1})\x1b[0m') | ||
|
||
try: | ||
install_option = int(input()) | ||
|
||
if install_option < 0 or install_option >= len(install_options): | ||
raise ValueError() | ||
except: | ||
print(f'\x1b[31;1mAborting.\x1b[0m') | ||
sys.exit(1) | ||
|
||
install_option = install_options[install_option]['id'] | ||
|
||
print('\x1b[33;1mPlease review the following before continuing:\x1b[0m') | ||
print(f'- Product to install: {internal["product_name"]}') | ||
print(f'- Installation option: {install_option}') | ||
print(f'- Install directory: {os.getcwd()}') | ||
print(f'- Python command/binary: {binary}\n') | ||
print('\x1b[33;1mProceed with installation? (y/n)\x1b[0m') | ||
|
||
try: | ||
answer = input().lower() | ||
except: | ||
print(f'\x1b[31;1mAborting.\x1b[0m') | ||
sys.exit(1) | ||
|
||
if not answer == 'y': | ||
print(f'\x1b[31;1mAborting.\x1b[0m') | ||
sys.exit(1) | ||
|
||
exit_code = os.system(f'{binary} boot/dep_installer.py {install_option}{options}') | ||
if not exit_code == 0: | ||
sys.exit(exit_code) | ||
|
||
exit_code = os.system(f'{binary} boot/installer.py {install_option}{options}') | ||
|
||
if not exit_code == 0: | ||
print('\x1b[31;1mInstaller has crashed or has been aborted.\x1b[0m') | ||
sys.exit(exit_code) | ||
|
||
# sleep to prevent 429s | ||
time.sleep(5) | ||
|
||
if not boot_file in os.listdir(): | ||
if os.path.isdir('update'): | ||
print(f'\x1b[33;1m{boot_file} is missing, copying from update folder.\x1b[0m') | ||
try: | ||
shutil.copy2(f'update/{boot_file}', boot_file) | ||
except: | ||
print(f'\x1b[31;1mCould not find {boot_file}. Your installation may be corrupted.\x1b[0m') | ||
print(f'Please install a fresh copy of {internal["product_name"]} from {internal["repo"]}.') | ||
sys.exit(1) | ||
|
||
first_boot = False | ||
last_boot = time.time() | ||
|
||
print(f'\x1b[36;1mStarting {internal["product_name"]}...\x1b[0m') | ||
|
||
if '.restart' in os.listdir(): | ||
os.remove('.restart') | ||
print('\x1b[33;1mAn incomplete restart was detected.\x1b[0m') | ||
|
||
while True: | ||
exit_code = os.system(f'{binary} {boot_file}{options}') | ||
|
||
crash_reboot = False | ||
if not exit_code == 0: | ||
diff = time.time() - last_boot | ||
if autoreboot and first_boot and diff > threshold: | ||
print(f'\x1b[31;1m{internal["product_name"]} has crashed, restarting...\x1b[0m') | ||
crash_reboot = True | ||
else: | ||
print(f'\x1b[31;1m{internal["product_name"]} has crashed.\x1b[0m') | ||
sys.exit(exit_code) | ||
|
||
if crash_reboot or '.restart' in os.listdir(): | ||
if '.restart' in os.listdir(): | ||
os.remove('.restart') | ||
|
||
print(f'\x1b[33;1mRestarting {internal["product_name"]}...\x1b[0m') | ||
else: | ||
print(f'\x1b[36;1m{internal["product_name"]} shutdown successful.\x1b[0m') | ||
sys.exit(0) | ||
|
||
first_boot = True | ||
last_boot = time.time() | ||
|
||
# sleep to prevent 429s | ||
time.sleep(5) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import json | ||
import os | ||
import sys | ||
|
||
install_option = sys.argv[1] if len(sys.argv) > 1 else None | ||
|
||
install_options = [ | ||
{ | ||
'id': 'stable', | ||
'name': '\U0001F48E Standard', | ||
'description': 'Uses the latest stable Nextcord version.', | ||
'default': True, | ||
'prefix': '', | ||
'color': '\x1b[32' | ||
} | ||
] | ||
|
||
prefix = None | ||
|
||
if not install_option: | ||
for option in install_options: | ||
if option['default']: | ||
install_option = option['id'] | ||
break | ||
else: | ||
for option in install_options: | ||
if option['id'] == install_option: | ||
prefix = option['prefix'] | ||
if prefix == '': | ||
prefix = None | ||
break | ||
|
||
boot_config = {} | ||
try: | ||
with open('boot_config.json') as file: | ||
boot_config = json.load(file) | ||
except: | ||
pass | ||
|
||
binary = boot_config['bootloader'].get('binary', 'py -3' if sys.platform == 'win32' else 'python3') | ||
|
||
print('\x1b[36;1mInstalling dependencies, this may take a while...\x1b[0m') | ||
|
||
if prefix: | ||
code = os.system(f'{binary} -m pip install --user -U -r requirements_{prefix}.txt') | ||
else: | ||
code = os.system(f'{binary} -m pip install --user -U -r requirements.txt') | ||
|
||
if not code == 0: | ||
print('\x1b[31;1mCould not install dependencies.\x1b[0m') | ||
sys.exit(code) | ||
|
||
print('\x1b[36;1mDependencies successfully installed.\x1b[0m') | ||
sys.exit(0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
import asyncio | ||
import sys | ||
import getpass | ||
import json | ||
import nextcord | ||
import tomli | ||
import tomli_w | ||
import traceback | ||
from nextcord.ext import commands | ||
|
||
install_option = sys.argv[1] if len(sys.argv) > 1 else None | ||
|
||
install_options = [ | ||
{ | ||
'id': 'stable', | ||
'name': '\U0001F48E Standard', | ||
'description': 'Uses the latest stable Nextcord version.', | ||
'default': True, | ||
'prefix': '', | ||
'color': '\x1b[32' | ||
} | ||
] | ||
|
||
if not install_option: | ||
for option in install_options: | ||
if option['default']: | ||
install_option = option['id'] | ||
break | ||
|
||
bot = commands.Bot( | ||
command_prefix='u!', | ||
intents=nextcord.Intents.all() | ||
) | ||
|
||
user_id = 0 | ||
server_id = 0 | ||
|
||
with open('boot/internal.json') as file: | ||
internal = json.load(file) | ||
|
||
if sys.version_info.minor < internal['required_py_version']: | ||
print(f'\x1b[31;49mCannot install {internal["product_name"]}. Python 3.{internal["required_py_version"]} or later is required.\x1b[0m') | ||
sys.exit(1) | ||
|
||
@bot.event | ||
async def on_ready(): | ||
global server_id | ||
|
||
print(f'\x1b[33;1mIs {bot.user.name} ({bot.user.id}) the correct bot? (y/n)\x1b[0m') | ||
answer = input().lower() | ||
|
||
if not answer == 'y': | ||
print(f'\x1b[31;1mAborting.\x1b[0m') | ||
sys.exit(1) | ||
|
||
print(f'\x1b[36;1mAttempting to DM user {user_id}...\x1b[0m') | ||
|
||
user = bot.get_user(user_id) | ||
|
||
for guild in bot.guilds: | ||
for member in guild.members: | ||
if member.id == user_id: | ||
server_id = guild.id | ||
break | ||
|
||
if not server_id == 0: | ||
break | ||
|
||
available = 10 | ||
tries = 0 | ||
|
||
while True: | ||
try: | ||
await user.send('If you can see this message, please return to the console, then type "y".') | ||
break | ||
except: | ||
tries += 1 | ||
|
||
if tries >= available: | ||
print(f'\x1b[31;1mCould not DM user after {available} attempts, aborting.\x1b[0m') | ||
sys.exit(1) | ||
if user: | ||
print(f'\x1b[33;1mCould not DM user. Please enable your DMs for a server you and the bot share.\x1b[0m') | ||
else: | ||
print(f'\x1b[33;1mCould not find user. Please add the bot to a server you are in.\x1b[0m') | ||
print(f'Use this link to add the bot: https://discord.com/api/oauth2/authorize?client_id={bot.user.id}&permissions=537259008&scope=bot') | ||
print(f'\x1b[33;1mTrying again in 30 seconds, {available-tries} tries remaining. Press Ctrl+C to abort.\x1b[0m') | ||
|
||
try: | ||
await asyncio.sleep(30) | ||
except: | ||
print(f'\x1b[31;1mAborting.\x1b[0m') | ||
sys.exit(1) | ||
|
||
print(f'\x1b[33;1mDid you receive a DM from the bot? (y/n)\x1b[0m') | ||
answer = input().lower() | ||
|
||
if not answer == 'y': | ||
print(f'\x1b[31;1mAborting.\x1b[0m') | ||
sys.exit(1) | ||
|
||
print('\x1b[36;1mOwner verified successfully, closing bot.\x1b[0m') | ||
await bot.close() | ||
|
||
print('\x1b[33;1mWe need the ID of the user who will be the instance owner. In most cases this is your user ID.\x1b[0m') | ||
print(f'\x1b[33;1mThe owner will have access to special commands for maintaining your {internal["product_name"]} instance.\x1b[0m') | ||
print('\x1b[33;1mTo copy your ID, go to your Discord settings, then Advanced, then enable Developer mode.\x1b[0m') | ||
|
||
while True: | ||
try: | ||
user_id = int(input()) | ||
break | ||
except KeyboardInterrupt: | ||
print('\x1b[31;49mAborted.\x1b[0m') | ||
sys.exit(1) | ||
except: | ||
print('\x1b[31;49mThis isn\'t an integer, try again.\x1b[0m') | ||
|
||
print('\x1b[33;1mWe will now ask for your bot token.\x1b[0m') | ||
print('\x1b[33;1mThe user verifier will use this token to log on to Discord.\x1b[0m\n') | ||
print(f'\x1b[37;41;1mWARNING: DO NOT SHARE THIS TOKEN, NOT EVEN WITH {internal["maintainer"].upper()}.\x1b[0m') | ||
print(f'\x1b[31;49m{internal["maintainer"]} will NEVER ask for your token. Please keep this token to yourself and only share it with trusted instance maintainers.\x1b[0m') | ||
print('\x1b[31;49mFor security reasons, the installer will hide the input.\x1b[0m') | ||
|
||
token = getpass.getpass() | ||
|
||
print('\x1b[36;1mStarting bot...\x1b[0m') | ||
|
||
try: | ||
bot.run(token) | ||
except: | ||
traceback.print_exc() | ||
print('\x1b[31;49mLogin failed. Perhaps your token is invalid?\x1b[0m') | ||
print('\x1b[31;49mMake sure all privileged intents are enabled for the bot.\x1b[0m') | ||
sys.exit(1) | ||
|
||
file = open('.env','w+') | ||
file.write(f'TOKEN={token}') | ||
Check failure Code scanning / CodeQL Clear-text storage of sensitive information High
This expression stores
sensitive data (password) Error loading related location Loading |
||
file.close() | ||
|
||
with open('config.toml', 'rb') as file: | ||
config = tomli.load(file) | ||
|
||
config['roles']['owner'] = user_id | ||
config['moderation']['home_guild'] = server_id | ||
|
||
with open('config.toml', 'wb') as file: | ||
tomli_w.dump(config, file) | ||
|
||
with open('.install.json','w+') as file: | ||
json.dump( | ||
{ | ||
'product': internal["product"], | ||
'setup': False, | ||
'option': install_option | ||
}, | ||
file | ||
) | ||
|
||
print(f'\x1b[36;1m{internal["product_name"]} installed successfully.\x1b[0m') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"maintainer": "UnifierHQ", | ||
"product": "unifier-micro", | ||
"product_name": "Unifier Micro", | ||
"base_bootfile": "microfier.py", | ||
"repo": "https://github.com/UnifierHQ/unifier-micro", | ||
"required_py_version": 9 | ||
} |
Oops, something went wrong.