Compare commits

...

3 Commits

14 changed files with 411 additions and 204 deletions

View File

@ -25,4 +25,4 @@ If applicable, add screenshots to help explain your problem.
- Python Version [e.g. 3.7.4]
**Additional context**
Add any other context about the problem here.
<-- Add any other context about the problem here. -->

View File

@ -1,16 +1,22 @@
<component name="ProjectDictionaryState">
<dictionary name="romain">
<words>
<w>asctime</w>
<w>commandstats</w>
<w>francais</w>
<w>ipinfo</w>
<w>iplocalise</w>
<w>jishaku</w>
<w>levelname</w>
<w>localiseip</w>
<w>postgresql</w>
<w>releaselevel</w>
<w>socketstats</w>
<w>splt</w>
<w>systemd</w>
<w>tutux</w>
<w>tuxbot</w>
<w>tuxbot's</w>
<w>tuxvenv</w>
<w>webhooks</w>
</words>

View File

@ -39,12 +39,12 @@ Continue to [create the venv](#creating-the-virtual-environnement).
#### Windows
*go to hell*
*not for now and not for the future*
## Creating the Virtual Environnement
## Creating the Virtual Environment
To set up the virtual environnement and install the bot, simply run this two commands:
To set up the virtual environment and install the bot, simply run this two commands:
```shell script
make

View File

@ -24,11 +24,11 @@ install_requires =
certifi==2020.4.5.1
chardet==3.0.4
ciso8601==2.1.3
click==7.1.2
colorama==0.4.3
discord-flags==2.1.1
discord.py==1.3.3
discord.py==1.3.4
dnspython==1.16.0
flatten-dict==0.3.0
humanize==2.4.0
idna==2.9
import-expression==1.1.3
@ -43,13 +43,13 @@ install_requires =
pytz==2020.1
regex==2020.6.7
requests==2.23.0
rich==6.0.0
six==1.15.0
toml>=0.9.4
tortoise-orm==0.16.13
typed-ast>=1.4.0
typing-extensions==3.7.4.2
urllib3==1.25.9
websockets==8.1
wheel==0.34.2
yarl==1.4.2

View File

@ -1,3 +1,5 @@
from setuptools import setup
setup(python_requires=">=3.7")
setup(
python_requires=">=3.7",
)

View File

@ -1,10 +1,16 @@
import subprocess
import os
from collections import namedtuple
build = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode()
build = os.popen('git rev-parse --short HEAD').read().strip()
info = os.popen('git log -n 1 -s --format="%s"').read().strip()
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel build")
version_info = VersionInfo(major=3, minor=0, micro=0, releaselevel="alpha", build=build)
VersionInfo = namedtuple(
"VersionInfo", "major minor micro releaselevel build, info"
)
version_info = VersionInfo(
major=3, minor=0, micro=0,
releaselevel="alpha", build=build, info=info
)
__version__ = "v{}.{}.{}-{}.{}".format(
version_info.major,
@ -13,3 +19,9 @@ __version__ = "v{}.{}.{}-{}.{}".format(
version_info.releaselevel,
version_info.build,
).replace("\n", "")
class ExitCodes:
CRITICAL = 1
SHUTDOWN = 0
RESTART = 42

View File

@ -1,27 +1,34 @@
import argparse
import asyncio
import getpass
import json
import logging
import platform
import signal
import sys
import os
from argparse import Namespace
from typing import NoReturn
import discord
import pip
from colorama import Fore, init, Style
from pip._vendor import distro
import tracemalloc
from rich.columns import Columns
from rich.console import Console
from rich.panel import Panel
from rich.traceback import install
from rich.table import Table, box
from rich.text import Text
from rich import print
import tuxbot.logging
from tuxbot.core import data_manager
from tuxbot.core.bot import Tux, ExitCodes
from tuxbot.core.utils.functions.cli import bordered
from . import __version__
from tuxbot.core.bot import Tux
from . import __version__, version_info, ExitCodes
log = logging.getLogger("tuxbot.main")
init()
console = Console()
install(console=console)
tracemalloc.start()
def list_instances() -> NoReturn:
@ -29,21 +36,38 @@ def list_instances() -> NoReturn:
"""
with data_manager.config_file.open() as fs:
datas = json.load(fs)
data = json.load(fs)
info = {"title": "Instances", "rows": []}
console.print(
Panel("[bold green]Instances", style="green"),
justify="center"
)
console.print()
for instance, details in datas.items():
info["rows"].append(
f"-> {instance} " f"{'up' if details.get('IS_RUNNING') else 'down'}"
columns = Columns(expand=True, padding=2, align="center")
for instance, details in data.items():
is_running = details.get('IS_RUNNING')
table = Table(
style="dim", border_style="not dim",
box=box.HEAVY_HEAD
)
table.add_column("Name")
table.add_column(("Running" if is_running else "Down") + " since")
table.add_row(instance, "42")
table.title = Text(
instance,
style="green" if is_running else "red"
)
columns.add_renderable(table)
console.print(columns)
console.print()
print(bordered(info))
sys.exit(0)
sys.exit(os.EX_OK)
def debug_info() -> NoReturn:
"""Show debug infos relatives to the bot
"""Show debug info relatives to the bot
"""
python_version = sys.version.replace("\n", "")
@ -51,29 +75,65 @@ def debug_info() -> NoReturn:
tuxbot_version = __version__
dpy_version = discord.__version__
os_info = distro.linux_distribution()
os_info = f"{os_info[0]} {os_info[1]}"
uptime = os.popen('uptime').read().strip().split()
runner = getpass.getuser()
console.print(
Panel("[bold blue]Debug Info", style="blue"),
justify="center"
)
console.print()
info = {
"title": "Debug Info",
"rows": [
f"Tuxbot version: {tuxbot_version}",
"",
f"Python version: {python_version}",
f"Python executable path: {sys.executable}",
f"Pip version: {pip_version}",
f"Discord.py version: {dpy_version}",
"",
f"OS info: {os_info}",
f"System arch: {platform.machine()}",
f"User: {runner}",
],
}
columns = Columns(expand=True, padding=2, align="center")
print(bordered(info))
sys.exit(0)
table = Table(
style="dim", border_style="not dim",
box=box.HEAVY_HEAD
)
table.add_column(
"Bot Info",
)
table.add_row(f"[u]Tuxbot version:[/u] {tuxbot_version}")
table.add_row(f"[u]Major:[/u] {version_info.major}")
table.add_row(f"[u]Minor:[/u] {version_info.minor}")
table.add_row(f"[u]Micro:[/u] {version_info.micro}")
table.add_row(f"[u]Level:[/u] {version_info.releaselevel}")
table.add_row(f"[u]Last change:[/u] {version_info.info}")
columns.add_renderable(table)
table = Table(
style="dim", border_style="not dim",
box=box.HEAVY_HEAD
)
table.add_column(
"Python Info",
)
table.add_row(f"[u]Python version:[/u] {python_version}")
table.add_row(f"[u]Python executable path:[/u] {sys.executable}")
table.add_row(f"[u]Pip version:[/u] {pip_version}")
table.add_row(f"[u]Discord.py version:[/u] {dpy_version}")
columns.add_renderable(table)
table = Table(
style="dim", border_style="not dim",
box=box.HEAVY_HEAD
)
table.add_column(
"Server Info",
)
table.add_row(f"[u]System:[/u] {os.uname().sysname}")
table.add_row(f"[u]System arch:[/u] {os.uname().machine}")
table.add_row(f"[u]Kernel:[/u] {os.uname().release}")
table.add_row(f"[u]User:[/u] {os.getlogin()}")
table.add_row(f"[u]Uptime:[/u] {uptime[2]}")
table.add_row(
f"[u]Load Average:[/u] {' '.join(map(str, os.getloadavg()))}"
)
columns.add_renderable(table)
console.print(columns)
console.print()
sys.exit(os.EX_OK)
def parse_cli_flags(args: list) -> Namespace:
@ -92,13 +152,19 @@ def parse_cli_flags(args: list) -> Namespace:
usage="tuxbot <instance_name> [arguments]",
)
parser.add_argument(
"--version", "-V", action="store_true", help="Show tuxbot's used version"
"--version", "-V", action="store_true",
help="Show tuxbot's used version"
)
parser.add_argument("--debug", action="store_true", help="Show debug information.")
parser.add_argument(
"--list-instances", "-L", action="store_true", help="List all instance names"
"--debug", action="store_true",
help="Show debug information."
)
parser.add_argument("--token", "-T", type=str, help="Run Tuxbot with passed token")
parser.add_argument(
"--list-instances", "-L", action="store_true",
help="List all instance names"
)
parser.add_argument("--token", "-T", type=str,
help="Run Tuxbot with passed token")
parser.add_argument(
"instance_name",
nargs="?",
@ -126,7 +192,6 @@ async def shutdown_handler(tux: Tux, signal_type, exit_code=None) -> NoReturn:
"""
if signal_type:
log.info("%s received. Quitting...", signal_type)
sys.exit(ExitCodes.SHUTDOWN)
elif exit_code is None:
log.info("Shutting down from unhandled exception")
tux.shutdown_code = ExitCodes.CRITICAL
@ -134,15 +199,7 @@ async def shutdown_handler(tux: Tux, signal_type, exit_code=None) -> NoReturn:
if exit_code is not None:
tux.shutdown_code = exit_code
try:
await tux.logout()
finally:
pending = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
for task in pending:
task.cancel()
await asyncio.gather(*pending, return_exceptions=True)
await tux.shutdown()
async def run_bot(tux: Tux, cli_flags: Namespace) -> None:
@ -178,10 +235,17 @@ async def run_bot(tux: Tux, cli_flags: Namespace) -> None:
try:
await tux.load_packages()
await tux.start(token, bot=True)
console.print()
await tux.start(token=token, bot=True)
except discord.LoginFailure:
log.critical("This token appears to be valid.")
console.print()
console.print(
"[prompt.invalid]This token appears to be valid. [i]exiting...[/i]"
)
sys.exit(ExitCodes.CRITICAL)
except Exception as e:
raise e
return None
@ -198,19 +262,19 @@ def main() -> NoReturn:
elif cli_flags.debug:
debug_info()
elif cli_flags.version:
print("Tuxbot V3")
print(f"Tuxbot V{version_info.major}")
print(f"Complete Version: {__version__}")
sys.exit(0)
sys.exit(os.EX_OK)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
if not cli_flags.instance_name:
print(
Fore.RED + "No instance provided ! "
console.print(
"[red]No instance provided ! "
"You can use 'tuxbot -L' to list all available instances"
+ Style.RESET_ALL
)
sys.exit(ExitCodes.CRITICAL)
@ -222,13 +286,12 @@ def main() -> NoReturn:
loop.run_until_complete(run_bot(tux, cli_flags))
except KeyboardInterrupt:
print(
Fore.RED
+ "Please use <prefix>quit instead of Ctrl+C to Shutdown!"
+ Style.RESET_ALL
console.print(
" [red]Please use <prefix>quit instead of Ctrl+C to Shutdown!"
)
log.warning("Please use <prefix>quit instead of Ctrl+C to Shutdown!")
log.error("Received KeyboardInterrupt")
console.print("[i]Trying to shutdown...")
if tux is not None:
loop.run_until_complete(shutdown_handler(tux, signal.SIGINT))
except SystemExit as exc:
@ -236,6 +299,7 @@ def main() -> NoReturn:
if tux is not None:
loop.run_until_complete(shutdown_handler(tux, None, exc.code))
except Exception as exc:
console.print_exception()
log.exception("Unexpected exception (%s): ", type(exc), exc_info=exc)
if tux is not None:
loop.run_until_complete(shutdown_handler(tux, None, 1))
@ -247,8 +311,12 @@ def main() -> NoReturn:
loop.stop()
loop.close()
exit_code = ExitCodes.CRITICAL if tux is None else tux.shutdown_code
sys.exit(exit_code)
if __name__ == "__main__":
main()
try:
main()
except Exception:
console.print_exception()

View File

@ -3,14 +3,14 @@ from collections import namedtuple
from .admin import Admin
from ...core.bot import Tux
VersionInfo = namedtuple("VersionInfo", "major minor micro releaselevel")
version_info = VersionInfo(major=2, minor=0, micro=0, releaselevel="alpha")
VersionInfo = namedtuple("VersionInfo", "major minor micro release_level")
version_info = VersionInfo(major=2, minor=0, micro=0, release_level="alpha")
__version__ = "v{}.{}.{}-{}".format(
version_info.major,
version_info.minor,
version_info.micro,
version_info.releaselevel,
version_info.release_level,
).replace("\n", "")

View File

@ -24,6 +24,7 @@ class Admin(commands.Cog, name="Admin"):
@checks.is_admin()
async def _lang(self, ctx: ContextPlus):
"""Manage lang settings."""
pass
@_lang.command(name="set", aliases=["define", "choice"])
async def _lang_set(self, ctx: ContextPlus, lang: str):

View File

@ -15,7 +15,7 @@ class Warnings(commands.Cog, name="Warnings"):
@commands.guild_only()
@checks.is_mod()
async def _warn(self, ctx: commands.Context):
pass
division_by_zero = 1 / 0
@_warn.command(name="add")
@commands.guild_only()

View File

@ -1,8 +1,10 @@
from colorama import init
from .. import __version__, version_info, VersionInfo
from .. import __version__, version_info, VersionInfo, ExitCodes
from .config import Config
__all__ = ["Config", "__version__", "version_info", "VersionInfo"]
init()
__all__ = [
"Config",
"__version__",
"version_info",
"VersionInfo",
"ExitCodes",
]

View File

@ -5,18 +5,25 @@ import sys
from typing import List, Union
import discord
from colorama import Fore, Style, init
from discord.ext import commands
from rich import box
from rich.columns import Columns
from rich.console import Console
from rich.panel import Panel
from rich.progress import Progress, TextColumn, BarColumn
from rich.table import Table
from rich.traceback import install
from tuxbot import version_info
from . import Config
from .data_manager import logs_data_path
from .utils.functions.cli import bordered
from . import __version__
from . import __version__, ExitCodes
from .utils.functions.extra import ContextPlus
log = logging.getLogger("tuxbot")
init()
console = Console()
install(console=console)
NAME = r"""
_____ _ _ _ _
@ -31,6 +38,13 @@ packages: List[str] = ["jishaku", "tuxbot.cogs.warnings", "tuxbot.cogs.admin"]
class Tux(commands.AutoShardedBot):
_loading: asyncio.Task
_progress = {
'main': Progress(
TextColumn("[bold blue]{task.fields[task_name]}", justify="right"),
BarColumn()
),
'tasks': {}
}
def __init__(self, *args, cli_flags=None, **kwargs):
# by default, if the bot shutdown without any intervention,
@ -71,52 +85,86 @@ class Tux(commands.AutoShardedBot):
async def load_packages(self):
if packages:
print("Loading packages...")
for package in packages:
try:
self.load_extension(package)
except Exception as e:
print(
Fore.RED
+ f"Failed to load package {package}"
+ Style.RESET_ALL
+ f" check "
f"{str((self.logs / 'tuxbot.log').resolve())} "
f"for more details"
)
with Progress() as progress:
task = progress.add_task(
"Loading packages...",
total=len(packages)
)
log.exception(f"Failed to load package {package}", exc_info=e)
for package in packages:
try:
self.load_extension(package)
progress.console.print(f"{package} loaded")
except Exception as e:
log.exception(
f"Failed to load package {package}",
exc_info=e
)
progress.console.print(
f"[red]Failed to load package {package} "
f"[i](see "
f"{str((self.logs / 'tuxbot.log').resolve())} "
f"for more details)[/i]"
)
progress.advance(task)
async def on_ready(self):
self.uptime = datetime.datetime.now()
INFO = {
"title": "INFO",
"rows": [
str(self.user),
f"Prefixes: {', '.join(self.config('core').get('prefixes'))}",
f"Language: {self.config('core').get('locale')}",
f"Tuxbot Version: {__version__}",
f"Discord.py Version: {discord.__version__}",
"Python Version: " + sys.version.replace("\n", ""),
f"Shards: {self.shard_count}",
f"Servers: {len(self.guilds)}",
f"Users: {len(self.users)}",
],
}
self._progress.get("main").stop_task(
self._progress.get("tasks")["connecting"]
)
self._progress.get("main").remove_task(
self._progress.get("tasks")["connecting"]
)
console.clear()
COGS = {"title": "COGS", "rows": []}
console.print(
Panel(f"[bold blue]Tuxbot V{version_info.major}", style="blue"),
justify="center"
)
console.print()
columns = Columns(expand=True, padding=2, align="center")
table = Table(
style="dim", border_style="not dim",
box=box.HEAVY_HEAD
)
table.add_column(
"INFO",
)
table.add_row(str(self.user))
table.add_row(f"Prefixes: {', '.join(self.config('core').get('prefixes'))}")
table.add_row(f"Language: {self.config('core').get('locale')}")
table.add_row(f"Tuxbot Version: {__version__}")
table.add_row(f"Discord.py Version: {discord.__version__}")
table.add_row(f"Shards: {self.shard_count}")
table.add_row(f"Servers: {len(self.guilds)}")
table.add_row(f"Users: {len(self.users)}")
columns.add_renderable(table)
table = Table(
style="dim", border_style="not dim",
box=box.HEAVY_HEAD
)
table.add_column(
"COGS",
)
for extension in packages:
COGS["rows"].append(
f"[{'X' if extension in self.extensions else ' '}] {extension}"
)
if extension in self.extensions:
status = f"[green]:heavy_check_mark: {extension} "
else:
status = f"[red]:cross_mark: {extension} "
print(Fore.LIGHTBLUE_EX + NAME)
print(Style.RESET_ALL)
print(bordered(INFO, COGS))
table.add_row(status)
columns.add_renderable(table)
print(f"\n{'=' * 118}\n\n")
console.print(columns)
console.print()
async def is_owner(self, user: Union[discord.User, discord.Member]) -> bool:
async def is_owner(self,
user: Union[discord.User, discord.Member]) -> bool:
"""Determines if the user is a bot owner.
Parameters
@ -152,9 +200,9 @@ class Tux(commands.AutoShardedBot):
return
if (
message.guild.id in self.config.get_blacklist("guild")
or message.channel.id in self.config.get_blacklist("channel")
or message.author.id in self.config.get_blacklist("user")
message.guild.id in self.config.get_blacklist("guild")
or message.channel.id in self.config.get_blacklist("channel")
or message.author.id in self.config.get_blacklist("user")
):
return
@ -168,11 +216,45 @@ class Tux(commands.AutoShardedBot):
async def on_message(self, message: discord.Message):
await self.process_commands(message)
async def start(self, token, bot):
"""Connect to Discord and start all connections.
Todo: add postgresql connect here
"""
with self._progress.get("main") as pg:
task_id = self._progress.get("tasks")["connecting"] = pg.add_task(
"connecting",
task_name="Connecting to Discord...", start=False
)
pg.update(task_id)
await super().start(token, bot=bot)
async def logout(self):
"""Disconnect from Discord and closes all actives connections.
Todo: add postgresql logout here
"""
for task in self._progress.get("tasks").keys():
self._progress.get("main").log("Shutting down", task)
self._progress.get("main").stop_task(
self._progress.get("tasks")[task]
)
self._progress.get("main").remove_task(
self._progress.get("tasks")["connecting"]
)
self._progress.get("main").stop()
pending = [
t for t in asyncio.all_tasks() if
t is not asyncio.current_task()
]
for task in pending:
console.log("Canceling", task.get_name(), f"({task.get_coro()})")
task.cancel()
await asyncio.gather(*pending, return_exceptions=True)
await super().logout()
async def shutdown(self, *, restart: bool = False):
@ -192,9 +274,3 @@ class Tux(commands.AutoShardedBot):
await self.logout()
sys.exit(self.shutdown_code)
class ExitCodes:
CRITICAL = 1
SHUTDOWN = 0
RESTART = 42

View File

@ -33,10 +33,12 @@ def init_logging(level: int, location: pathlib.Path) -> None:
)
dpy_handler = logging.handlers.RotatingFileHandler(
str(dpy_logger_file.resolve()), maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
str(dpy_logger_file.resolve()),
maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
)
base_handler = logging.handlers.RotatingFileHandler(
str(base_logger_file.resolve()), maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
str(base_logger_file.resolve()),
maxBytes=MAX_BYTES, backupCount=MAX_OLD_LOGS
)
stdout_handler = logging.StreamHandler(sys.stdout)

View File

@ -3,14 +3,19 @@ import logging
import re
import sys
from pathlib import Path
from typing import NoReturn, Union, List, Set
from typing import NoReturn, Union, List
import click
from colorama import Fore, Style, init
from rich.prompt import Prompt, IntPrompt
from rich.console import Console
from rich.rule import Rule
from rich.traceback import install
from rich import print
from tuxbot.core.data_manager import config_dir, app_dir
init()
console = Console()
console.clear()
install(console=console)
try:
config_dir.mkdir(parents=True, exist_ok=True)
@ -77,14 +82,15 @@ def get_name() -> str:
"""
name = ""
while not name:
print(
name = Prompt.ask(
"What name do you want to give this instance?\n"
"(valid characters: A-Z, a-z, 0-9, _, -)"
"[i](valid characters: A-Z, a-z, 0-9, _, -)[/i]\n",
default="prod",
console=console
)
name = input("> ")
if re.fullmatch(r"[a-zA-Z0-9_\-]*", name) is None:
print()
print(Fore.RED + "ERROR: Invalid characters provided" + Style.RESET_ALL)
print("[prompt.invalid]ERROR: Invalid characters provided")
name = ""
return name
@ -113,42 +119,35 @@ def get_data_dir(instance_name: str) -> Path:
except OSError:
print()
print(
Fore.RED + f"mkdir: cannot create directory '{path}':"
f" Permission denied" + Style.RESET_ALL
f"mkdir: cannot create directory '{path}': Permission denied"
)
path = ""
return path
while not data_path_input:
print(
"where do you want to save the configurations?\n"
"Press [enter] to keep the default path"
data_path_input = Path(
Prompt.ask(
"where do you want to save the configurations?",
default=str(data_path),
console=console
)
)
print()
print(f"Default: {data_path}")
data_path_input = input("> ")
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 != "":
data_path_input = Path(data_path_input)
try:
exists = data_path_input.exists()
except OSError:
print()
print(
Fore.RED + "Impossible to verify the validity of the path, "
"make sure it does not contain any invalid characters."
+ Style.RESET_ALL
)
data_path_input = ""
exists = False
if data_path_input and not exists:
data_path_input = make_data_dir(data_path_input)
else:
data_path_input = make_data_dir(data_path)
if data_path_input and not exists:
data_path_input = make_data_dir(data_path_input)
print()
print(
@ -156,7 +155,11 @@ def get_data_dir(instance_name: str) -> Path:
f"`{instance_name}` instance"
)
if not click.confirm("Please confirm", default=True):
if Prompt.ask(
"Please confirm",
choices=["y", "n"], default="y",
console=console
) != "y":
print("Rerun the process to redo this configuration.")
sys.exit(0)
@ -178,25 +181,23 @@ def get_token() -> str:
token = ""
while not token:
print(
"Please enter the bot token\n"
"(you can find it at https://discord.com/developers/applications)"
token = Prompt.ask(
"Please enter the bot token "
"(you can find it at https://discord.com/developers/applications)",
console=console
)
token = input("> ")
if (
re.fullmatch(
r"([a-zA-Z0-9]{24}\.[a-zA-Z0-9_]{6}\.[a-zA-Z0-9_\-]{27}|mfa\.[a-zA-Z0-9_\-]{84})",
token,
)
is None
):
print(Fore.RED + "ERROR: Invalid token provided" + Style.RESET_ALL)
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")
token = ""
return token
def get_multiple(
question: str, confirmation: str, value_type: type
question: str, confirmation: str, value_type: type
) -> List[Union[str, int]]:
"""Give possibility to user to fill multiple value.
@ -214,15 +215,29 @@ def get_multiple(
List[Union[str, int]]
List containing user filled values.
"""
print(question)
user_input = input("> ")
prompt = IntPrompt if value_type is int else Prompt
user_input = prompt.ask(question, console=console)
if not user_input:
return []
values = [user_input]
while click.confirm(confirmation, default=False):
values.append(value_type(input("> ")))
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]"
)
return values
@ -236,24 +251,23 @@ def additional_config() -> dict:
Dict with cog name as key and configs as value.
"""
p = Path(r"tuxbot/cogs").glob("**/additional_config.json")
datas = {}
data = {}
for file in p:
print()
print("\n" * 4)
cog_name = str(file.parent).split("/")[-1]
datas[cog_name] = {}
data[cog_name] = {}
with file.open("r") as f:
data = json.load(f)
print(f"\n==Configuration for `{cog_name}` module==")
print(Rule(f"\nConfiguration for `{cog_name}` module"))
for key, value in data.items():
print()
print(value["description"])
datas[cog_name][key] = input("> ")
data[cog_name][key] = Prompt.ask(value["description"])
return datas
return data
def finish_setup(data_dir: Path) -> NoReturn:
@ -264,15 +278,29 @@ def finish_setup(data_dir: Path) -> NoReturn:
data_dir:Path
Where to save configs.
"""
print("Now, it's time to finish this setup by giving bot informations\n")
print(
Rule(
"Now, it's time to finish this setup by giving bot information"
)
)
print()
token = get_token()
print()
prefixes = get_multiple(
"Choice a (or multiple) prefix for the bot", "Add another prefix ?", str
"Choice a (or multiple) prefix for the bot", "Add another prefix ?",
str
)
mentionable = click.confirm("Does the bot answer if it's mentioned?", default=True)
print()
mentionable = Prompt.ask(
"Does the bot answer if it's mentioned?",
choices=["y", "n"],
default="y"
) == "y"
print()
owners_id = get_multiple(
"Give the owner id of this bot", "Add another owner ?", int
)
@ -305,7 +333,12 @@ def basic_setup() -> NoReturn:
"""Configs who refer to instances.
"""
print("Hi ! it's time for you to give me informations about you instance")
print(
Rule(
"Hi ! it's time for you to give me information about you instance"
)
)
print()
name = get_name()
data_dir = get_data_dir(name)
@ -318,11 +351,14 @@ def basic_setup() -> NoReturn:
if name in instances_list:
print()
print(
Fore.RED + f"WARNING: An instance named `{name}` already exists "
f"Continuing will overwrite this instance configs." + Style.RESET_ALL
console.print(
f"WARNING: An instance named `{name}` already exists "
f"Continuing will overwrite this instance configs.", style="red"
)
if not click.confirm("Are you sure you want to continue?", default=False):
if Prompt.ask(
"Are you sure you want to continue?",
choices=["y", "n"], default="n"
) == "n":
print("Abandon...")
sys.exit(0)
@ -357,6 +393,8 @@ def setup() -> NoReturn:
basic_setup()
except KeyboardInterrupt:
print("Exiting...")
except:
console.print_exception()
if __name__ == "__main__":