fix(commands|Custom>alias): close TUXBOT-BOT-1A, close TUXBOT-BOT-1H, close TUXBOT-BOT-1J

This commit is contained in:
Romain J 2021-02-16 19:28:30 +01:00
parent c7ddba1bae
commit b5ca338d6c
19 changed files with 198 additions and 365 deletions

3
.gitignore vendored
View file

@ -43,3 +43,6 @@ build
.env .env
.envs/* .envs/*
!.envs/.local/ !.envs/.local/
data/settings/

View file

@ -1,38 +1,74 @@
PYTHON = python ifeq ($(ISPROD), 1)
VENV = venv DOCKER_LOCAL := docker-compose -f production.yml
else
DOCKER_LOCAL := docker-compose -f local.yml
endif
XGETTEXT_FLAGS = --no-wrap --language='python' --keyword=_ --from-code='UTF-8' --msgid-bugs-address='rick@gnous.eu' --width=79 --package-name='Tuxbot-bot' INSTANCE := preprod
DOCKER_TUXBOT := $(DOCKER_LOCAL) run --rm tuxbot
VIRTUAL_ENV := venv
PYTHON_PATH := $(VIRTUAL_ENV)/bin/python
XGETTEXT_FLAGS := --no-wrap --language='python' --keyword=_ --from-code='UTF-8' --msgid-bugs-address='rick@gnous.eu' --width=79 --package-name='Tuxbot-bot'
# Init # Init
.PHONY: main
main: main:
$(PYTHON) -m venv --clear $(VENV) $(PYTHON_PATH) -m venv --clear $(VENV)
$(VENV)/bin/pip install -U pip setuptools $(VIRTUAL_ENV)/bin/pip install -U pip setuptools
install:
$(VENV)/bin/pip install . .PHONY: install
install-dev: install:
$(VENV)/bin/pip install -r dev.requirements.txt $(VIRTUAL_ENV)/bin/pip install .
update:
$(VENV)/bin/pip install --upgrade --force-reinstall . .PHONY: install-dev
update_soft: install-dev:
$(VENV)/bin/pip install --upgrade . $(VIRTUAL_ENV)/bin/pip install -r dev.requirements.txt
.PHONY: update
update:
$(VIRTUAL_ENV)/bin/pip install --upgrade .
.PHONY: update-all
update-all:
$(VIRTUAL_ENV)/bin/pip install --upgrade --force-reinstall .
.PHONY: dev
dev: black update
tuxbot
# Docker
.PHONY: docker
docker:
$(DOCKER_LOCAL) build
$(DOCKER_LOCAL) up -d
.PHONY: docker-start
docker-start:
$(DOCKER_TUXBOT) tuxbot
dev: black update_soft
tuxbot dev
# Blackify code # Blackify code
.PHONY: black
black: black:
$(PYTHON) -m black `git ls-files "*.py"` --line-length=79 && $(PYTHON) -m pylint tuxbot $(PYTHON_PATH) -m black `git ls-files "*.py"` --line-length=79 && $(PYTHON_PATH) -m pylint tuxbot
# Translations # Translations
.PHONY: xgettext
xgettext: xgettext:
for cog in tuxbot/cogs/*/; do \ for cog in tuxbot/cogs/*/; do \
xgettext `find $$cog -type f -name '*.py'` --output=$$cog/locales/messages.pot $(XGETTEXT_FLAGS); \ xgettext `find $$cog -type f -name '*.py'` --output=$$cog/locales/messages.pot $(XGETTEXT_FLAGS); \
done done
.PHONY: msginit
msginit: msginit:
for cog in tuxbot/cogs/*/; do \ for cog in tuxbot/cogs/*/; do \
msginit --input=$$cog/locales/messages.pot --output=$$cog/locales/fr-FR.po --locale=fr_FR.UTF-8 --no-translator; \ msginit --input=$$cog/locales/messages.pot --output=$$cog/locales/fr-FR.po --locale=fr_FR.UTF-8 --no-translator; \
msginit --input=$$cog/locales/messages.pot --output=$$cog/locales/en-US.po --locale=en_US.UTF-8 --no-translator; \ msginit --input=$$cog/locales/messages.pot --output=$$cog/locales/en-US.po --locale=en_US.UTF-8 --no-translator; \
done done
.PHONY: msgmerge
msgmerge: msgmerge:
for cog in tuxbot/cogs/*/; do \ for cog in tuxbot/cogs/*/; do \
msgmerge --update $$cog/locales/fr-FR.po $$cog/locales/messages.pot; \ msgmerge --update $$cog/locales/fr-FR.po $$cog/locales/messages.pot; \

View file

@ -14,7 +14,7 @@ Installing the pre-requirements
- The pre-requirements are: - The pre-requirements are:
- Python 3.8 or greater - Python 3.9 or greater
- Pip - Pip
- Git - Git
@ -134,7 +134,7 @@ To update the whole bot after a :bash:`git pull`, just execute
$ make update $ make update
.. |image0| image:: https://img.shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10-%23007ec6 .. |image0| image:: https://img.shields.io/badge/python-3.9%20%7C%203.10-%23007ec6
.. |image1| image:: https://img.shields.io/github/issues/Rom1-J/tuxbot-bot .. |image1| image:: https://img.shields.io/github/issues/Rom1-J/tuxbot-bot
.. |image2| image:: https://img.shields.io/badge/code%20style-black-000000.svg .. |image2| image:: https://img.shields.io/badge/code%20style-black-000000.svg
.. |image3| image:: https://wakatime.com/badge/github/Rom1-J/tuxbot-bot.svg .. |image3| image:: https://wakatime.com/badge/github/Rom1-J/tuxbot-bot.svg

View file

@ -1,4 +1,4 @@
FROM python:3.8-slim-buster FROM python:3.9-slim-buster
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 1 ENV PYTHONDONTWRITEBYTECODE 1
@ -19,6 +19,7 @@ RUN apt-get update \
# Requirements are installed here to ensure they will be cached. # Requirements are installed here to ensure they will be cached.
COPY ./dev.requirements.txt /app/dev.requirements.txt COPY ./dev.requirements.txt /app/dev.requirements.txt
COPY ./tuxbot /app/tuxbot COPY ./tuxbot /app/tuxbot
COPY ./data /app/data
COPY ./setup.cfg /app/setup.cfg COPY ./setup.cfg /app/setup.cfg
COPY ./setup.py /app/setup.py COPY ./setup.py /app/setup.py
RUN pip install -r /app/dev.requirements.txt RUN pip install -r /app/dev.requirements.txt

View file

@ -1,13 +1,9 @@
FROM node:10-stretch-slim as client-builder FROM node:10-stretch-slim as client-builder
WORKDIR /app WORKDIR /app
COPY ./package.json /app
RUN npm install && npm cache clean --force
COPY . /app
RUN npm run build
# Python build stage # Python build stage
FROM python:3.8-slim-buster FROM python:3.9-slim-buster
ENV PYTHONUNBUFFERED 1 ENV PYTHONUNBUFFERED 1
@ -26,8 +22,6 @@ RUN addgroup --system tuxbot \
&& adduser --system --ingroup tuxbot tuxbot && adduser --system --ingroup tuxbot tuxbot
# Requirements are installed here to ensure they will be cached. # Requirements are installed here to ensure they will be cached.
RUN pip install --no-cache-dir psycopg2==2.8.6
COPY --chown=tuxbot:tuxbot ./compose/production/tuxbot/entrypoint /entrypoint COPY --chown=tuxbot:tuxbot ./compose/production/tuxbot/entrypoint /entrypoint
RUN sed -i 's/\r$//g' /entrypoint RUN sed -i 's/\r$//g' /entrypoint
RUN chmod +x /entrypoint RUN chmod +x /entrypoint

View file

@ -13,24 +13,25 @@ if [ -z "${POSTGRES_USER}" ]; then
fi fi
export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" export DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
echo "psql at: ${DATABASE_URL}"
postgres_ready() { postgres_ready() {
python << END python << END
import sys import sys
import psycopg2 import asyncpg
import asyncio
async def main():
try: try:
psycopg2.connect( conn = await asyncpg.connect('postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}')
dbname="${POSTGRES_DB}", except Exception:
user="${POSTGRES_USER}",
password="${POSTGRES_PASSWORD}",
host="${POSTGRES_HOST}",
port="${POSTGRES_PORT}",
)
except psycopg2.OperationalError:
sys.exit(-1) sys.exit(-1)
await conn.close()
sys.exit(0) sys.exit(0)
asyncio.get_event_loop().run_until_complete(main())
END END
} }
until postgres_ready; do until postgres_ready; do

View file

@ -9,6 +9,7 @@ services:
build: build:
context: . context: .
dockerfile: ./compose/local/tuxbot/Dockerfile dockerfile: ./compose/local/tuxbot/Dockerfile
restart: always
image: tuxbot_bot_local_tuxbot image: tuxbot_bot_local_tuxbot
container_name: tuxbot container_name: tuxbot
depends_on: depends_on:

30
production.yml Normal file
View file

@ -0,0 +1,30 @@
version: '3'
volumes:
production_postgres_data: {}
production_postgres_data_backups: {}
production_traefik: {}
services:
tuxbot:
build:
context: .
dockerfile: ./compose/production/tuxbot/Dockerfile
image: tuxbot_bot_production_tuxbot
depends_on:
- postgres
env_file:
- ./.envs/.production/.tuxbot
- ./.envs/.production/.postgres
command: /start
postgres:
build:
context: .
dockerfile: ./compose/production/postgres/Dockerfile
image: tuxbot_bot_production_postgres
volumes:
- production_postgres_data:/var/lib/postgresql/data:Z
- production_postgres_data_backups:/backups:z
env_file:
- ./.envs/.production/.postgres

View file

@ -13,9 +13,8 @@ platforms = linux
[options] [options]
packages = find_namespace: packages = find_namespace:
python_requires = >=3.8 python_requires = >=3.9
install_requires = install_requires =
appdirs==1.4.4
asyncpg==0.21.0 asyncpg==0.21.0
Babel==2.8.0 Babel==2.8.0
discord.py @ git+https://github.com/Rapptz/discord.py discord.py @ git+https://github.com/Rapptz/discord.py
@ -26,7 +25,7 @@ install_requires =
psutil==5.7.2 psutil==5.7.2
pydig==0.3.0 pydig==0.3.0
rich==9.10.0 rich==9.10.0
sentry_sdk==0.20.0 sentry_sdk>=0.20.2
structured_config==4.12 structured_config==4.12
tortoise-orm==0.16.17 tortoise-orm==0.16.17

View file

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

View file

@ -5,20 +5,16 @@ import signal
import sys import sys
import os import os
from argparse import Namespace from argparse import Namespace
from datetime import datetime
import discord import discord
import humanize
import pip import pip
from rich.columns import Columns from rich.columns import Columns
from rich.panel import Panel from rich.panel import Panel
from rich.table import Table, box from rich.table import Table, box
from rich.text import Text
from rich import print as rprint from rich import print as rprint
import tuxbot.logging import tuxbot.logging
from tuxbot.core.bot import Tux from tuxbot.core.bot import Tux
from tuxbot.core import config
from tuxbot.core.utils import data_manager from tuxbot.core.utils import data_manager
from tuxbot.core.utils.console import console from tuxbot.core.utils.console import console
from . import __version__, version_info, ExitCodes from . import __version__, version_info, ExitCodes
@ -28,41 +24,6 @@ log = logging.getLogger("tuxbot.main")
BORDER_STYLE = "not dim" BORDER_STYLE = "not dim"
def list_instances() -> None:
"""List all available instances"""
app_config = config.ConfigFile(
data_manager.config_dir / "config.yaml", config.AppConfig
).config
console.print(
Panel("[bold green]Instances", style="green"), justify="center"
)
console.print()
columns = Columns(expand=True, padding=2, align="center")
for instance, details in app_config.Instances.items():
active = details["active"]
last_run = (
humanize.naturaltime(
datetime.now() - datetime.fromtimestamp(details["last_run"])
)
or "[i]unknown"
)
table = Table(
style="dim", border_style=BORDER_STYLE, box=box.HEAVY_HEAD
)
table.add_column("Name")
table.add_column(("Running since" if active else "Last run"))
table.add_row(instance, last_run)
table.title = Text(instance, style="green" if active else "red")
columns.add_renderable(table)
console.print(columns)
console.print()
sys.exit(os.EX_OK)
def debug_info() -> None: def debug_info() -> None:
"""Show debug info relatives to the bot""" """Show debug info relatives to the bot"""
python_version = sys.version.replace("\n", "") python_version = sys.version.replace("\n", "")
@ -134,7 +95,7 @@ def parse_cli_flags(args: list) -> Namespace:
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Tuxbot - OpenSource bot", description="Tuxbot - OpenSource bot",
usage="tuxbot <instance_name> [arguments]", usage="tuxbot [arguments]",
) )
parser.add_argument( parser.add_argument(
"--version", "--version",
@ -145,20 +106,9 @@ def parse_cli_flags(args: list) -> Namespace:
parser.add_argument( parser.add_argument(
"--debug", action="store_true", help="Show debug information." "--debug", action="store_true", help="Show debug information."
) )
parser.add_argument(
"--list-instances",
"-L",
action="store_true",
help="List all instance names",
)
parser.add_argument( parser.add_argument(
"--token", "-T", type=str, help="Run Tuxbot with passed token" "--token", "-T", type=str, help="Run Tuxbot with passed token"
) )
parser.add_argument(
"instance_name",
nargs="?",
help="Name of the bot instance created during `tuxbot-setup`.",
)
args = parser.parse_args(args) args = parser.parse_args(args)
@ -206,7 +156,7 @@ async def run_bot(tux: Tux, cli_flags: Namespace) -> None:
None None
When exiting, this function return None. When exiting, this function return None.
""" """
data_path = data_manager.data_path(tux.instance_name) data_path = data_manager.data_path
tuxbot.logging.init_logging(10, location=data_path / "logs") tuxbot.logging.init_logging(10, location=data_path / "logs")
@ -245,9 +195,7 @@ def run() -> None:
tux = None tux = None
cli_flags = parse_cli_flags(sys.argv[1:]) cli_flags = parse_cli_flags(sys.argv[1:])
if cli_flags.list_instances: if cli_flags.debug:
list_instances()
elif cli_flags.debug:
debug_info() debug_info()
elif cli_flags.version: elif cli_flags.version:
rprint(f"Tuxbot V{version_info.major}") rprint(f"Tuxbot V{version_info.major}")
@ -259,13 +207,6 @@ def run() -> None:
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
try: try:
if not cli_flags.instance_name:
console.print(
"[red]No instance provided ! "
"You can use 'tuxbot -L' to list all available instances"
)
sys.exit(ExitCodes.CRITICAL)
tux = Tux( tux = Tux(
cli_flags=cli_flags, cli_flags=cli_flags,
description="Tuxbot, made from and for OpenSource", description="Tuxbot, made from and for OpenSource",

View file

@ -5,7 +5,7 @@ from discord.ext import commands
from tuxbot.cogs.Custom.functions.converters import AliasConvertor from tuxbot.cogs.Custom.functions.converters import AliasConvertor
from tuxbot.core.bot import Tux from tuxbot.core.bot import Tux
from tuxbot.core.config import set_for_key, search_for from tuxbot.core.config import set_for_key, search_for, set_if_none
from tuxbot.core.config import Config from tuxbot.core.config import Config
from tuxbot.core.i18n import ( from tuxbot.core.i18n import (
Translator, Translator,
@ -33,14 +33,14 @@ class Custom(commands.Cog, name="Custom"):
# ========================================================================= # =========================================================================
# ========================================================================= # =========================================================================
async def _get_aliases(self, ctx: ContextPlus) -> dict:
return search_for(self.bot.config.Users, ctx.author.id, "aliases")
async def _save_lang(self, ctx: ContextPlus, lang: str) -> None: async def _save_lang(self, ctx: ContextPlus, lang: str) -> None:
set_for_key( set_for_key(
self.bot.config.Users, ctx.author.id, Config.User, locale=lang self.bot.config.Users, ctx.author.id, Config.User, locale=lang
) )
async def _get_aliases(self, ctx: ContextPlus) -> dict:
return search_for(self.bot.config.Users, ctx.author.id, "aliases")
async def _save_alias(self, ctx: ContextPlus, alias: dict) -> None: async def _save_alias(self, ctx: ContextPlus, alias: dict) -> None:
set_for_key( set_for_key(
self.bot.config.Users, ctx.author.id, Config.User, alias=alias self.bot.config.Users, ctx.author.id, Config.User, alias=alias
@ -76,13 +76,17 @@ class Custom(commands.Cog, name="Custom"):
@_custom.command(name="alias", aliases=["aliases"]) @_custom.command(name="alias", aliases=["aliases"])
async def _custom_alias(self, ctx: ContextPlus, *, alias: AliasConvertor): async def _custom_alias(self, ctx: ContextPlus, *, alias: AliasConvertor):
args = alias.split(" | ") args = str(alias).split(" | ")
command = args[0] command = args[0]
alias = args[1] alias = args[1]
user_aliases = await self._get_aliases(ctx) user_aliases = await self._get_aliases(ctx)
if not user_aliases:
set_if_none(self.bot.config.Users, ctx.author.id, Config.User)
user_aliases = await self._get_aliases(ctx)
if alias in user_aliases.keys(): if alias in user_aliases.keys():
return await ctx.send( return await ctx.send(
_( _(

View file

@ -1,15 +1,10 @@
from structured_config import Structure, StrField from structured_config import Structure
HAS_MODELS = False HAS_MODELS = False
class DevConfig(Structure): class DevConfig(Structure):
sentryKey: str = StrField("") pass
extra = { extra = {}
"sentryKey": {
"type": str,
"description": "Sentry KEY for error logging (https://sentry.io/)",
},
}

View file

@ -56,9 +56,7 @@ class Logs(commands.Cog, name="Logs"):
self.gateway_worker.start() # pylint: disable=no-member self.gateway_worker.start() # pylint: disable=no-member
self.__config: LogsConfig = ConfigFile( self.__config: LogsConfig = ConfigFile(
str( str(cogs_data_path("Logs") / "config.yaml"),
cogs_data_path(self.bot.instance_name, "Logs") / "config.yaml"
),
LogsConfig, LogsConfig,
).config ).config
@ -266,6 +264,19 @@ class Logs(commands.Cog, name="Logs"):
e.timestamp = datetime.datetime.utcnow() e.timestamp = datetime.datetime.utcnow()
await self.webhook("errors").send(embed=e) await self.webhook("errors").send(embed=e)
e.description = _(
"```An error occurred, the bot owner has been advertised...```",
ctx,
self.bot.config,
)
e.remove_field(0)
e.remove_field(1)
e.remove_field(1)
e.set_footer(text=sentry_sdk.last_event_id())
await ctx.send(embed=e)
@commands.Cog.listener() @commands.Cog.listener()
async def on_socket_raw_send(self, data): async def on_socket_raw_send(self, data):
if '"op":2' not in data and '"op":6' not in data: if '"op":2' not in data and '"op":6' not in data:

View file

@ -54,10 +54,7 @@ class Network(commands.Cog, name="Network"):
def __init__(self, bot: Tux): def __init__(self, bot: Tux):
self.bot = bot self.bot = bot
self.__config: NetworkConfig = ConfigFile( self.__config: NetworkConfig = ConfigFile(
str( str(cogs_data_path("Network") / "config.yaml"),
cogs_data_path(self.bot.instance_name, "Network")
/ "config.yaml"
),
NetworkConfig, NetworkConfig,
).config ).config

View file

@ -18,8 +18,7 @@ from tortoise import Tortoise
from tuxbot import version_info from tuxbot import version_info
from tuxbot.core.utils.data_manager import ( from tuxbot.core.utils.data_manager import (
logs_data_path, logs_data_path,
data_path, config_file,
config_dir,
) )
from tuxbot.core.utils.functions.extra import ContextPlus from tuxbot.core.utils.functions.extra import ContextPlus
from tuxbot.core.utils.functions.prefix import get_prefixes from tuxbot.core.utils.functions.prefix import get_prefixes
@ -28,8 +27,6 @@ from tuxbot.core.config import (
Config, Config,
ConfigFile, ConfigFile,
search_for, search_for,
AppConfig,
set_for_key,
) )
from . import __version__, ExitCodes from . import __version__, ExitCodes
from . import exceptions from . import exceptions
@ -57,17 +54,15 @@ class Tux(commands.AutoShardedBot):
# it's a crash # it's a crash
self.shutdown_code = ExitCodes.CRITICAL self.shutdown_code = ExitCodes.CRITICAL
self.cli_flags = cli_flags self.cli_flags = cli_flags
self.instance_name = self.cli_flags.instance_name
self.last_exception = None self.last_exception = None
self.logs = logs_data_path(self.instance_name) self.logs = logs_data_path()
self.console = console self.console = console
self.stats = {"commands": Counter(), "socket": Counter()} self.stats = {"commands": Counter(), "socket": Counter()}
self.config: Config = ConfigFile( self.config: Config = ConfigFile(config_file, Config).config
str(data_path(self.instance_name) / "config.yaml"), Config self.instance_name = self.config.Core.instance_name
).config
async def _prefixes(bot, message) -> List[str]: async def _prefixes(bot, message) -> List[str]:
prefixes = self.config.Core.prefixes prefixes = self.config.Core.prefixes
@ -157,14 +152,6 @@ class Tux(commands.AutoShardedBot):
self.uptime = datetime.datetime.now() self.uptime = datetime.datetime.now()
self.last_on_ready = self.uptime self.last_on_ready = self.uptime
app_config = ConfigFile(config_dir / "config.yaml", AppConfig).config
set_for_key(
app_config.Instances,
self.instance_name,
AppConfig.Instance,
active=True,
last_run=datetime.datetime.timestamp(self.uptime),
)
with self._progress["main"] as progress: with self._progress["main"] as progress:
progress.stop_task(self._progress["tasks"]["discord_connecting"]) progress.stop_task(self._progress["tasks"]["discord_connecting"])
@ -189,6 +176,7 @@ class Tux(commands.AutoShardedBot):
table.add_row(f"Language: {self.config.Core.locale}") table.add_row(f"Language: {self.config.Core.locale}")
table.add_row(f"Tuxbot Version: {__version__}") table.add_row(f"Tuxbot Version: {__version__}")
table.add_row(f"Discord.py Version: {discord.__version__}") table.add_row(f"Discord.py Version: {discord.__version__}")
table.add_row(f"Instance name: {self.instance_name}")
table.add_row(f"Shards: {self.shard_count}") table.add_row(f"Shards: {self.shard_count}")
table.add_row(f"Servers: {len(self.guilds)}") table.add_row(f"Servers: {len(self.guilds)}")
table.add_row(f"Users: {len(self.users)}") table.add_row(f"Users: {len(self.users)}")
@ -315,8 +303,8 @@ class Tux(commands.AutoShardedBot):
task_id = self._progress["tasks"][ task_id = self._progress["tasks"][
"discord_connecting" "discord_connecting"
] = progress.add_task( ] = progress.add_task(
"discord_connecting", "Connecting to Discord...",
task_name="Connecting to Discord...", task_name="discord_connecting",
start=False, start=False,
) )
progress.update(task_id) progress.update(task_id)
@ -327,14 +315,6 @@ class Tux(commands.AutoShardedBot):
Todo: add postgresql logout here Todo: add postgresql logout here
""" """
app_config = ConfigFile(config_dir / "config.yaml", AppConfig).config
set_for_key(
app_config.Instances,
self.instance_name,
AppConfig.Instance,
active=False,
)
with self._progress["main"] as progress: with self._progress["main"] as progress:
for task in self._progress["tasks"]: for task in self._progress["tasks"]:
progress.log("Shutting down", task) progress.log("Shutting down", task)

View file

@ -11,7 +11,6 @@ from structured_config import (
__all__ = [ __all__ = [
"Config", "Config",
"ConfigFile", "ConfigFile",
"AppConfig",
"search_for", "search_for",
"set_for_key", "set_for_key",
"set_for", "set_for",
@ -60,20 +59,7 @@ class Config(Structure):
mentionable: bool = BoolField("") mentionable: bool = BoolField("")
locale: str = StrField("") locale: str = StrField("")
disabled_command: List[str] = [] disabled_command: List[str] = []
instance_name: str = StrField("")
# =============================================================================
# Configuration of Tuxbot Application (not the bot)
# =============================================================================
class AppConfig(Structure):
class Instance(Structure):
path: str = StrField("")
active: bool = BoolField(False)
last_run: int = IntField(0)
Instances: Dict[str, Instance] = {}
# ============================================================================= # =============================================================================
@ -87,6 +73,11 @@ def search_for(config, key, value, default=False) -> Any:
return default return default
def set_if_none(config, key, ctype) -> None:
if key not in config:
config[key] = ctype()
def set_for_key(config, key, ctype, **values) -> None: def set_for_key(config, key, ctype, **values) -> None:
# pylint: disable=anomalous-backslash-in-string # pylint: disable=anomalous-backslash-in-string
""" """
@ -105,8 +96,7 @@ def set_for_key(config, key, ctype, **values) -> None:
rip roxy .*' / .*' ; .*`- +' `*' rip roxy .*' / .*' ; .*`- +' `*'
201?-2020 :,( `*-* `*-* `*-*' 201?-2020 :,( `*-* `*-* `*-*'
""" """
if key not in config: set_if_none(config, key, ctype)
config[key] = ctype()
for k, v in values.items(): for k, v in values.items():
setattr(config[key], k, v) setattr(config[key], k, v)

View file

@ -1,51 +1,32 @@
import logging import logging
from pathlib import Path from pathlib import Path
import os
import appdirs
log = logging.getLogger("tuxbot.core.data_manager") log = logging.getLogger("tuxbot.core.data_manager")
app_dir = appdirs.AppDirs("Tuxbot-bot") core_path = Path(os.getcwd())
config_dir = Path(app_dir.user_config_dir)
config_file = config_dir / "config.yaml" data_path = core_path / "data"
config_path = data_path / "settings"
config_file = config_path / "config.yaml"
def data_path(instance_name: str) -> Path: def logs_data_path() -> Path:
"""Return Path for data configs.
Parameters
----------
instance_name:str
Returns
-------
Path
Generated path for data configs.
"""
return Path(app_dir.user_data_dir) / "data" / instance_name
def logs_data_path(instance_name: str) -> Path:
"""Return Path for Logs. """Return Path for Logs.
Parameters
----------
instance_name:str
Returns Returns
------- -------
Path Path
Generated path for Logs files. Generated path for Logs files.
""" """
return data_path(instance_name) / "logs" return data_path / "logs"
def cogs_data_path(instance_name: str, cog_name: str = "") -> Path: def cogs_data_path(cog_name: str = "") -> Path:
"""Return Path for cogs. """Return Path for cogs.
Parameters Parameters
---------- ----------
instance_name:str
cog_name:str cog_name:str
Returns Returns
@ -53,4 +34,4 @@ def cogs_data_path(instance_name: str, cog_name: str = "") -> Path:
Path Path
Generated path for cogs configs. Generated path for cogs configs.
""" """
return data_path(instance_name) / "cogs" / cog_name return data_path / "settings" / "cogs" / cog_name

View file

@ -17,31 +17,26 @@ from rich.style import Style
from rich.traceback import install from rich.traceback import install
from tuxbot import version_info from tuxbot import version_info
from tuxbot.core.config import set_for, set_for_key from tuxbot.core.config import set_for
from tuxbot.logging import formatter from tuxbot.logging import formatter
from tuxbot.core.utils.data_manager import config_dir, app_dir, cogs_data_path from tuxbot.core.utils.data_manager import (
config_path,
config_file,
cogs_data_path,
)
from tuxbot.core import config from tuxbot.core import config
console = Console() console = Console()
install(console=console) install(console=console, show_locals=True)
try: try:
config_dir.mkdir(parents=True, exist_ok=True) config_path.mkdir(parents=True, exist_ok=True)
except PermissionError: except PermissionError:
console.print( console.print(
f"mkdir: cannot create directory '{config_dir}': Permission denied" f"mkdir: cannot create directory '{config_path}': Permission denied"
) )
sys.exit(1) sys.exit(1)
app_config = config.ConfigFile(
config_dir / "config.yaml", config.AppConfig
).config
if not app_config.Instances:
instances_list = []
else:
instances_list = list(app_config.Instances.keys())
def get_name() -> str: def get_name() -> str:
"""Get instance name via input. """Get instance name via input.
@ -66,80 +61,6 @@ def get_name() -> str:
return name return name
def get_data_dir(instance_name: str) -> Path:
"""Returning data path.
Parameters
----------
instance_name:str
Instance name.
Returns
-------
Path
The data config path corresponding to the instance.
"""
data_path = Path(app_dir.user_data_dir) / "data" / instance_name
data_path_input = ""
console.print()
def make_data_dir(path: Path) -> Union[Path, str]:
try:
path.mkdir(parents=True, exist_ok=True)
except OSError:
console.print()
console.print(
f"mkdir: cannot create directory '{path}': Permission denied"
)
path = ""
return path
while not data_path_input:
data_path_input = Path(
Prompt.ask(
"where do you want to save the configurations?",
default=str(data_path),
console=console,
)
)
try:
exists = data_path_input.exists()
except OSError:
console.print()
console.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)
console.print()
console.print(
f"You have chosen {data_path_input} to be your config directory for "
f"`{instance_name}` instance"
)
if (
Prompt.ask(
"Please confirm", choices=["y", "n"], default="y", console=console
)
!= "y"
):
console.print("Rerun the process to redo this configuration.")
sys.exit(0)
(data_path_input / "Logs").mkdir(parents=True, exist_ok=True)
return data_path_input
def get_token() -> str: def get_token() -> str:
"""Get token via input. """Get token via input.
@ -250,7 +171,7 @@ def get_extra(question: str, value_type: type) -> Union[str, int]:
return prompt.ask(question, console=console) return prompt.ask(question, console=console)
def additional_config(instance: str, cogs: str = "**"): def additional_config(cogs: str = "**"):
"""Asking for additional configs in cogs. """Asking for additional configs in cogs.
Returns Returns
@ -277,7 +198,7 @@ def additional_config(instance: str, cogs: str = "**"):
mod_extra = mod.extra mod_extra = mod.extra
mod_config = config.ConfigFile( mod_config = config.ConfigFile(
str(cogs_data_path(instance, cog_name) / "config.yaml"), str(cogs_data_path(cog_name) / "config.yaml"),
mod_config_type, mod_config_type,
).config ).config
@ -296,14 +217,10 @@ def additional_config(instance: str, cogs: str = "**"):
) )
def finish_setup(data_dir: Path) -> None: def finish_setup() -> None:
"""Configs who directly refer to the bot. """Configs who directly refer to the bot."""
name = get_name()
Parameters
----------
data_dir:Path
Where to save configs.
"""
console.print( console.print(
Rule("Now, it's time to finish this setup by giving bot information") Rule("Now, it's time to finish this setup by giving bot information")
) )
@ -363,22 +280,21 @@ def finish_setup(data_dir: Path) -> None:
), ),
} }
instance_config = config.ConfigFile( _config_file = config.ConfigFile(str(config_file), config.Config)
str(data_dir / "config.yaml"), config.Config
)
instance_config.config.Core.owners_id = owners_id _config_file.config.Core.owners_id = owners_id
instance_config.config.Core.prefixes = prefixes _config_file.config.Core.prefixes = prefixes
instance_config.config.Core.token = token _config_file.config.Core.token = token
instance_config.config.Core.ip = ip _config_file.config.Core.ip = ip
instance_config.config.Core.mentionable = mentionable _config_file.config.Core.mentionable = mentionable
instance_config.config.Core.locale = "en-US" _config_file.config.Core.locale = "en-US"
_config_file.config.Core.instance_name = name
instance_config.config.Core.Database.username = database["username"] _config_file.config.Core.Database.username = database["username"]
instance_config.config.Core.Database.password = database["password"] _config_file.config.Core.Database.password = database["password"]
instance_config.config.Core.Database.domain = database["domain"] _config_file.config.Core.Database.domain = database["domain"]
instance_config.config.Core.Database.port = database["port"] _config_file.config.Core.Database.port = database["port"]
instance_config.config.Core.Database.db_name = database["db_name"] _config_file.config.Core.Database.db_name = database["db_name"]
def basic_setup() -> None: def basic_setup() -> None:
@ -388,47 +304,15 @@ def basic_setup() -> None:
"Hi ! it's time for you to give me information about you instance" "Hi ! it's time for you to give me information about you instance"
) )
) )
console.print()
name = get_name()
data_dir = get_data_dir(name) finish_setup()
if name in instances_list:
console.print()
console.print(
f"WARNING: An instance named `{name}` already exists "
f"Continuing will overwrite this instance configs.",
style="red",
)
if (
Prompt.ask(
"Are you sure you want to continue?",
choices=["y", "n"],
default="n",
)
== "n"
):
console.print("Abandon...")
sys.exit(0)
set_for_key(
app_config.Instances,
name,
config.AppConfig.Instance,
path=str(data_dir.resolve()),
active=False,
)
console.print("\n" * 4)
finish_setup(data_dir)
console.print() console.print()
console.print( console.print(
f"Instance successfully created! " "Instance successfully created! "
f"You can now run `tuxbot {name}` to launch this instance now or " "You can now run `tuxbot` to launch it now or "
f"setup the additional configs by running " "setup the additional configs by running "
f"`tuxbot-setup {name} --additional-config=all`" "`tuxbot-setup --additional-config=all`"
) )
@ -440,10 +324,7 @@ def update() -> None:
) )
if response.get("sha")[:6] == version_info.build: if response.get("sha")[:6] == version_info.build:
print( print("Nothing to update, you can run `tuxbot` " "to start the bot")
"Nothing to update, you can run `tuxbot [instance_name]` "
"to start the bot"
)
else: else:
print(f"Updating to {response.get('sha')[:6]}...") print(f"Updating to {response.get('sha')[:6]}...")
@ -465,12 +346,7 @@ def parse_cli_flags(args: list) -> Namespace:
""" """
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Tuxbot Setup - OpenSource bot", description="Tuxbot Setup - OpenSource bot",
usage="tuxbot-setup [instance] [arguments]", usage="tuxbot-setup [arguments]",
)
parser.add_argument(
"instance_name",
nargs="?",
help="Name of the bot instance to edit.",
) )
parser.add_argument( parser.add_argument(
"-a", "-a",
@ -507,15 +383,8 @@ def setup() -> None:
stdout_handler.setFormatter(formatter) stdout_handler.setFormatter(formatter)
base_logger.addHandler(stdout_handler) base_logger.addHandler(stdout_handler)
if cli_flags.additional_config and not cli_flags.instance_name: if cli_flags.additional_config:
console.print( additional_config(cli_flags.additional_config)
"[red]No instance to modify provided ! "
"You can use 'tuxbot -L' to list all available instances"
)
elif cli_flags.instance_name:
additional_config(
cli_flags.instance_name, cli_flags.additional_config
)
else: else:
console.clear() console.clear()
basic_setup() basic_setup()