2020-06-03 18:24:38 +02:00
|
|
|
import json
|
|
|
|
import logging
|
|
|
|
import re
|
|
|
|
import sys
|
|
|
|
from pathlib import Path
|
2020-08-26 17:15:38 +02:00
|
|
|
from typing import NoReturn, Union, List
|
2020-06-03 18:24:38 +02:00
|
|
|
|
2020-08-26 17:15:38 +02:00
|
|
|
from rich.prompt import Prompt, IntPrompt
|
|
|
|
from rich.console import Console
|
|
|
|
from rich.rule import Rule
|
2020-08-28 01:06:57 +02:00
|
|
|
from rich.traceback import install
|
2020-08-26 17:15:38 +02:00
|
|
|
from rich import print
|
2020-06-03 18:24:38 +02:00
|
|
|
|
2020-06-03 19:41:30 +02:00
|
|
|
from tuxbot.core.data_manager import config_dir, app_dir
|
2020-06-03 18:24:38 +02:00
|
|
|
|
2020-08-26 17:15:38 +02:00
|
|
|
console = Console()
|
|
|
|
console.clear()
|
2020-08-28 01:06:57 +02:00
|
|
|
install(console=console)
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
except PermissionError:
|
|
|
|
print(f"mkdir: cannot create directory '{config_dir}': Permission denied")
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
config_file = config_dir / "config.json"
|
|
|
|
|
|
|
|
|
|
|
|
def load_existing_config() -> dict:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Loading and returning configs.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
dict
|
|
|
|
a dict containing all configurations.
|
|
|
|
|
|
|
|
"""
|
2020-06-03 18:24:38 +02:00
|
|
|
if not config_file.exists():
|
|
|
|
return {}
|
|
|
|
|
|
|
|
with config_file.open() as fs:
|
|
|
|
return json.load(fs)
|
|
|
|
|
|
|
|
|
|
|
|
instances_data = load_existing_config()
|
|
|
|
if not instances_data:
|
|
|
|
instances_list = []
|
|
|
|
else:
|
|
|
|
instances_list = list(instances_data.keys())
|
|
|
|
|
|
|
|
|
2020-06-04 00:46:53 +02:00
|
|
|
def save_config(name: str, data: dict, delete=False) -> NoReturn:
|
|
|
|
"""save data in config file.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name:str
|
|
|
|
name of instance.
|
|
|
|
data:dict
|
|
|
|
settings for `name` instance.
|
|
|
|
delete:bool
|
|
|
|
delete or no data.
|
|
|
|
"""
|
2020-06-03 18:24:38 +02:00
|
|
|
_config = load_existing_config()
|
|
|
|
|
|
|
|
if delete and name in _config:
|
|
|
|
_config.pop(name)
|
|
|
|
else:
|
|
|
|
_config[name] = data
|
|
|
|
|
|
|
|
with config_file.open("w") as fs:
|
|
|
|
json.dump(_config, fs, indent=4)
|
|
|
|
|
|
|
|
|
|
|
|
def get_name() -> str:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Get instance name via input.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
str
|
|
|
|
The instance name choose by user.
|
|
|
|
"""
|
2020-06-03 18:24:38 +02:00
|
|
|
name = ""
|
|
|
|
while not name:
|
2020-08-26 17:15:38 +02:00
|
|
|
name = Prompt.ask(
|
2020-06-03 18:24:38 +02:00
|
|
|
"What name do you want to give this instance?\n"
|
2020-08-26 17:15:38 +02:00
|
|
|
"[i](valid characters: A-Z, a-z, 0-9, _, -)[/i]\n",
|
|
|
|
default="prod",
|
|
|
|
console=console
|
2020-06-03 18:24:38 +02:00
|
|
|
)
|
|
|
|
if re.fullmatch(r"[a-zA-Z0-9_\-]*", name) is None:
|
|
|
|
print()
|
2020-08-26 17:15:38 +02:00
|
|
|
print("[prompt.invalid]ERROR: Invalid characters provided")
|
2020-06-03 18:24:38 +02:00
|
|
|
name = ""
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
|
|
def get_data_dir(instance_name: str) -> Path:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Returning data path.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
instance_name:str
|
|
|
|
Instance name.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
Path
|
|
|
|
The data config path corresponding to the instance.
|
|
|
|
|
|
|
|
"""
|
2020-06-03 18:24:38 +02:00
|
|
|
data_path = Path(app_dir.user_data_dir) / "data" / instance_name
|
|
|
|
data_path_input = ""
|
|
|
|
print()
|
|
|
|
|
|
|
|
def make_data_dir(path: Path) -> Union[Path, str]:
|
|
|
|
try:
|
|
|
|
path.mkdir(parents=True, exist_ok=True)
|
|
|
|
except OSError:
|
|
|
|
print()
|
|
|
|
print(
|
2020-08-26 17:15:38 +02:00
|
|
|
f"mkdir: cannot create directory '{path}': Permission denied"
|
2020-06-03 18:24:38 +02:00
|
|
|
)
|
|
|
|
path = ""
|
|
|
|
|
|
|
|
return path
|
|
|
|
|
|
|
|
while not data_path_input:
|
2020-08-26 17:15:38 +02:00
|
|
|
data_path_input = Path(
|
|
|
|
Prompt.ask(
|
|
|
|
"where do you want to save the configurations?",
|
|
|
|
default=str(data_path),
|
|
|
|
console=console
|
|
|
|
)
|
2020-06-03 18:24:38 +02:00
|
|
|
)
|
2020-08-26 17:15:38 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
exists = data_path_input.exists()
|
|
|
|
except OSError:
|
|
|
|
print()
|
|
|
|
print(
|
|
|
|
"[prompt.invalid]"
|
|
|
|
"Impossible to verify the validity of the path,"
|
|
|
|
" make sure it does not contain any invalid characters."
|
|
|
|
)
|
|
|
|
data_path_input = ""
|
|
|
|
exists = False
|
|
|
|
|
|
|
|
if data_path_input and not exists:
|
|
|
|
data_path_input = make_data_dir(data_path_input)
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
print()
|
|
|
|
print(
|
|
|
|
f"You have chosen {data_path_input} to be your config directory for "
|
|
|
|
f"`{instance_name}` instance"
|
|
|
|
)
|
|
|
|
|
2020-08-26 17:15:38 +02:00
|
|
|
if Prompt.ask(
|
|
|
|
"Please confirm",
|
|
|
|
choices=["y", "n"], default="y",
|
|
|
|
console=console
|
|
|
|
) != "y":
|
2020-06-03 18:24:38 +02:00
|
|
|
print("Rerun the process to redo this configuration.")
|
|
|
|
sys.exit(0)
|
|
|
|
|
2020-06-06 18:51:47 +02:00
|
|
|
(data_path_input / "core").mkdir(parents=True, exist_ok=True)
|
|
|
|
(data_path_input / "cogs").mkdir(parents=True, exist_ok=True)
|
|
|
|
(data_path_input / "logs").mkdir(parents=True, exist_ok=True)
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
return data_path_input
|
|
|
|
|
|
|
|
|
|
|
|
def get_token() -> str:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Get token via input.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
str
|
|
|
|
The token choose by user.
|
|
|
|
"""
|
2020-06-03 18:24:38 +02:00
|
|
|
token = ""
|
|
|
|
|
|
|
|
while not token:
|
2020-08-26 17:15:38 +02:00
|
|
|
token = Prompt.ask(
|
|
|
|
"Please enter the bot token "
|
|
|
|
"(you can find it at https://discord.com/developers/applications)",
|
|
|
|
console=console
|
2020-06-03 18:24:38 +02:00
|
|
|
)
|
2020-08-26 17:15:38 +02:00
|
|
|
if re.fullmatch(
|
|
|
|
r"([a-zA-Z0-9]{24}\.[a-zA-Z0-9_]{6}\.[a-zA-Z0-9_\-]{27}"
|
|
|
|
r"|mfa\.[a-zA-Z0-9_\-]{84})",
|
|
|
|
token) \
|
|
|
|
is None:
|
|
|
|
print("[prompt.invalid]ERROR: Invalid token provided")
|
2020-06-03 18:24:38 +02:00
|
|
|
token = ""
|
|
|
|
return token
|
|
|
|
|
|
|
|
|
2020-06-06 18:51:47 +02:00
|
|
|
def get_multiple(
|
2020-08-26 17:15:38 +02:00
|
|
|
question: str, confirmation: str, value_type: type
|
2020-06-06 18:51:47 +02:00
|
|
|
) -> List[Union[str, int]]:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Give possibility to user to fill multiple value.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
question:str
|
|
|
|
First question.
|
|
|
|
confirmation:str
|
|
|
|
Asking text if user want to add another.
|
|
|
|
value_type:type
|
|
|
|
The type of values inside the list.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
List[Union[str, int]]
|
|
|
|
List containing user filled values.
|
|
|
|
"""
|
2020-08-26 17:15:38 +02:00
|
|
|
prompt = IntPrompt if value_type is int else Prompt
|
|
|
|
|
|
|
|
user_input = prompt.ask(question, console=console)
|
|
|
|
|
2020-06-05 00:36:20 +02:00
|
|
|
if not user_input:
|
|
|
|
return []
|
|
|
|
|
|
|
|
values = [user_input]
|
2020-06-03 18:24:38 +02:00
|
|
|
|
2020-08-26 17:15:38 +02:00
|
|
|
while Prompt.ask(
|
|
|
|
confirmation,
|
|
|
|
choices=["y", "n"], default="y",
|
|
|
|
console=console
|
|
|
|
) != "n":
|
|
|
|
new = prompt.ask("Other")
|
|
|
|
|
|
|
|
if new not in values:
|
|
|
|
values.append(new)
|
|
|
|
else:
|
|
|
|
print(
|
|
|
|
f"[prompt.invalid]"
|
|
|
|
f"ERROR: `{new}` is already present, [i]ignored[/i]"
|
|
|
|
)
|
2020-06-03 18:24:38 +02:00
|
|
|
|
2020-06-04 00:14:50 +02:00
|
|
|
return values
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
def additional_config() -> dict:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Asking for additional configs in cogs.
|
|
|
|
|
|
|
|
Returns
|
|
|
|
-------
|
|
|
|
dict:
|
|
|
|
Dict with cog name as key and configs as value.
|
|
|
|
"""
|
2020-06-06 18:51:47 +02:00
|
|
|
p = Path(r"tuxbot/cogs").glob("**/additional_config.json")
|
2020-08-26 17:15:38 +02:00
|
|
|
data = {}
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
for file in p:
|
2020-08-26 17:15:38 +02:00
|
|
|
print("\n" * 4)
|
2020-06-06 18:51:47 +02:00
|
|
|
cog_name = str(file.parent).split("/")[-1]
|
2020-08-26 17:15:38 +02:00
|
|
|
data[cog_name] = {}
|
2020-06-03 18:24:38 +02:00
|
|
|
|
2020-06-06 18:51:47 +02:00
|
|
|
with file.open("r") as f:
|
2020-06-03 18:24:38 +02:00
|
|
|
data = json.load(f)
|
|
|
|
|
2020-08-26 17:15:38 +02:00
|
|
|
print(Rule(f"\nConfiguration for `{cog_name}` module"))
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
for key, value in data.items():
|
|
|
|
print()
|
2020-08-26 17:15:38 +02:00
|
|
|
data[cog_name][key] = Prompt.ask(value["description"])
|
2020-06-03 18:24:38 +02:00
|
|
|
|
2020-08-26 17:15:38 +02:00
|
|
|
return data
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
def finish_setup(data_dir: Path) -> NoReturn:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Configs who directly refer to the bot.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
data_dir:Path
|
|
|
|
Where to save configs.
|
|
|
|
"""
|
2020-08-26 17:15:38 +02:00
|
|
|
print(
|
|
|
|
Rule(
|
|
|
|
"Now, it's time to finish this setup by giving bot information"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
print()
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
token = get_token()
|
2020-08-26 17:15:38 +02:00
|
|
|
|
2020-06-03 18:24:38 +02:00
|
|
|
print()
|
2020-06-03 19:41:30 +02:00
|
|
|
prefixes = get_multiple(
|
2020-08-26 17:15:38 +02:00
|
|
|
"Choice a (or multiple) prefix for the bot", "Add another prefix ?",
|
|
|
|
str
|
2020-06-03 19:41:30 +02:00
|
|
|
)
|
|
|
|
|
2020-08-26 17:15:38 +02:00
|
|
|
print()
|
|
|
|
mentionable = Prompt.ask(
|
|
|
|
"Does the bot answer if it's mentioned?",
|
|
|
|
choices=["y", "n"],
|
|
|
|
default="y"
|
|
|
|
) == "y"
|
|
|
|
|
|
|
|
print()
|
2020-06-03 19:41:30 +02:00
|
|
|
owners_id = get_multiple(
|
2020-06-06 18:51:47 +02:00
|
|
|
"Give the owner id of this bot", "Add another owner ?", int
|
2020-06-03 19:41:30 +02:00
|
|
|
)
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
cogs_config = additional_config()
|
|
|
|
|
2020-06-06 18:51:47 +02:00
|
|
|
core_file = data_dir / "core" / "settings.json"
|
2020-06-03 18:24:38 +02:00
|
|
|
core = {
|
2020-06-06 18:51:47 +02:00
|
|
|
"token": token,
|
|
|
|
"prefixes": prefixes,
|
|
|
|
"mentionable": mentionable,
|
|
|
|
"owners_id": owners_id,
|
|
|
|
"locale": "en-US",
|
2020-06-03 18:24:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
with core_file.open("w") as fs:
|
|
|
|
json.dump(core, fs, indent=4)
|
|
|
|
|
|
|
|
for cog, data in cogs_config.items():
|
2020-06-06 18:51:47 +02:00
|
|
|
data_cog_dir = data_dir / "cogs" / cog
|
2020-06-03 18:24:38 +02:00
|
|
|
data_cog_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
2020-06-06 18:51:47 +02:00
|
|
|
data_cog_file = data_cog_dir / "settings.json"
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
with data_cog_file.open("w") as fs:
|
|
|
|
json.dump(data, fs, indent=4)
|
|
|
|
|
|
|
|
|
|
|
|
def basic_setup() -> NoReturn:
|
2020-06-04 00:46:53 +02:00
|
|
|
"""Configs who refer to instances.
|
|
|
|
|
|
|
|
"""
|
2020-08-26 17:15:38 +02:00
|
|
|
print(
|
|
|
|
Rule(
|
|
|
|
"Hi ! it's time for you to give me information about you instance"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
print()
|
2020-06-03 18:24:38 +02:00
|
|
|
name = get_name()
|
|
|
|
|
|
|
|
data_dir = get_data_dir(name)
|
|
|
|
|
|
|
|
configs = load_existing_config()
|
|
|
|
instance_config = configs[name] if name in instances_list else {}
|
|
|
|
|
|
|
|
instance_config["DATA_PATH"] = str(data_dir.resolve())
|
|
|
|
instance_config["IS_RUNNING"] = False
|
|
|
|
|
|
|
|
if name in instances_list:
|
|
|
|
print()
|
2020-08-26 17:15:38 +02:00
|
|
|
console.print(
|
|
|
|
f"WARNING: An instance named `{name}` already exists "
|
|
|
|
f"Continuing will overwrite this instance configs.", style="red"
|
2020-06-03 18:24:38 +02:00
|
|
|
)
|
2020-08-26 17:15:38 +02:00
|
|
|
if Prompt.ask(
|
|
|
|
"Are you sure you want to continue?",
|
|
|
|
choices=["y", "n"], default="n"
|
|
|
|
) == "n":
|
2020-06-03 18:24:38 +02:00
|
|
|
print("Abandon...")
|
|
|
|
sys.exit(0)
|
|
|
|
|
|
|
|
save_config(name, instance_config)
|
|
|
|
|
2020-06-06 18:51:47 +02:00
|
|
|
print("\n" * 4)
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
finish_setup(data_dir)
|
|
|
|
|
|
|
|
print()
|
|
|
|
print(
|
|
|
|
f"Instance successfully created! "
|
|
|
|
f"You can now run `tuxbot {name}` to launch this instance"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2020-06-04 00:46:53 +02:00
|
|
|
def setup() -> NoReturn:
|
2020-06-03 18:24:38 +02:00
|
|
|
try:
|
|
|
|
"""Create a new instance."""
|
|
|
|
level = logging.DEBUG
|
2020-06-03 19:41:30 +02:00
|
|
|
base_logger = logging.getLogger("tuxbot")
|
2020-06-03 18:24:38 +02:00
|
|
|
base_logger.setLevel(level)
|
|
|
|
formatter = logging.Formatter(
|
|
|
|
"[{asctime}] [{levelname}] {name}: {message}",
|
2020-06-06 18:51:47 +02:00
|
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
|
|
style="{",
|
2020-06-03 18:24:38 +02:00
|
|
|
)
|
|
|
|
stdout_handler = logging.StreamHandler(sys.stdout)
|
|
|
|
stdout_handler.setFormatter(formatter)
|
|
|
|
base_logger.addHandler(stdout_handler)
|
|
|
|
|
|
|
|
basic_setup()
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
print("Exiting...")
|
2020-08-28 01:06:57 +02:00
|
|
|
except:
|
|
|
|
console.print_exception()
|
2020-06-03 18:24:38 +02:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
setup()
|