Commit 8c9fab79 authored by Javinator9889's avatar Javinator9889 🎼

Merge branch 'yt_downloader' into 'development'

Suitable downloader for milestone #1

See merge request !3
parents b8d3794e 16784ed1
Pipeline #63 passed with stage
in 50 seconds
# https://hub.docker.com/r/library/python/tags/
image: python:latest
# Change pip's cache directory to be inside the project directory since we can
# only cache local items.
variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache:
paths:
- .cache/pip
- venv/
before_script:
- python -V # Print out python version for debugging
test:pylint:
script:
- pip install pylint
- pylint -j 0 --exit-zero --ignored-classes=_socketobject *.py YouTubeMDBot
test:
script:
- pip install -r YouTubeMDBot/requirements.txt
- python -m unittest $(pwd)/YouTubeMDBot/tests/*.py
-- MySQL Script generated by MySQL Workbench
-- lun 22 jul 2019 14:28:48 CEST
-- jue 25 jul 2019 13:50:06 CEST
-- Model: New Model Version: 1.0
-- MySQL Workbench Forward Engineering
......@@ -18,82 +18,6 @@ CREATE SCHEMA IF NOT EXISTS `youtubemd` DEFAULT CHARACTER SET utf8mb4 ;
SHOW WARNINGS;
USE `youtubemd` ;
-- -----------------------------------------------------
-- Table `youtubemd`.`User`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`User` (
`id` INT(64) NOT NULL DEFAULT 0,
`name` VARCHAR(45) NULL DEFAULT 'User',
`surname` VARCHAR(45) NULL,
`username` VARCHAR(45) NULL,
`lastSeen` DATETIME NOT NULL,
`firstUsage` DATETIME NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
CHECKSUM = 1
PACK_KEYS = 1;
SHOW WARNINGS;
CREATE UNIQUE INDEX `id_UNIQUE` ON `youtubemd`.`User` (`id` ASC) VISIBLE;
SHOW WARNINGS;
CREATE UNIQUE INDEX `username_UNIQUE` ON `youtubemd`.`User` (`username` ASC) VISIBLE;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`Preferences`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`Preferences` (
`language` VARCHAR(3) NOT NULL DEFAULT 'en',
`audioQuality` ENUM('320k', '256k', '128k') NOT NULL DEFAULT '128k',
`audioSampling` ENUM('44000', '48000') NOT NULL DEFAULT '44000',
`sendSongLinks` TINYINT NOT NULL DEFAULT 0,
`User_id` INT(64) NOT NULL,
PRIMARY KEY (`User_id`),
CONSTRAINT `fk_Preferences_User`
FOREIGN KEY (`User_id`)
REFERENCES `youtubemd`.`User` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
CHECKSUM = 1;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`Metadata`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`Metadata` (
`idMetadata` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL,
`artist` VARCHAR(60) NOT NULL,
`cover` BLOB NOT NULL,
`duration` INT NULL,
`customMetadata` TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (`idMetadata`))
ENGINE = InnoDB
AUTO_INCREMENT = 0
CHECKSUM = 1;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`VideoInformation`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`VideoInformation` (
`id` VARCHAR(11) NOT NULL,
`title` VARCHAR(100) NOT NULL,
`channel` VARCHAR(60) NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
CHECKSUM = 1;
SHOW WARNINGS;
CREATE UNIQUE INDEX `id_UNIQUE` ON `youtubemd`.`VideoInformation` (`id` ASC) VISIBLE;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`DownloadInformation`
-- -----------------------------------------------------
......@@ -128,6 +52,23 @@ CREATE INDEX `fk_DownloadInformation_VideoInformation1_idx` ON `youtubemd`.`Down
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`DownloadStatistics`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`DownloadStatistics` (
`timesRequested` INT NOT NULL DEFAULT 0,
`DownloadInformation_file_id` VARCHAR(50) NOT NULL,
PRIMARY KEY (`DownloadInformation_file_id`),
CONSTRAINT `fk_DownloadStatistics_DownloadInformation1`
FOREIGN KEY (`DownloadInformation_file_id`)
REFERENCES `youtubemd`.`DownloadInformation` (`file_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
CHECKSUM = 1;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`History`
-- -----------------------------------------------------
......@@ -154,35 +95,18 @@ CREATE INDEX `fk_History_DownloadInformation1_idx` ON `youtubemd`.`History` (`Do
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`VideoStatistics`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`VideoStatistics` (
`timesRequested` INT NOT NULL DEFAULT 0,
`VideoInformation_id` VARCHAR(11) NOT NULL,
PRIMARY KEY (`VideoInformation_id`),
CONSTRAINT `fk_VideoStatistics_VideoInformation1`
FOREIGN KEY (`VideoInformation_id`)
REFERENCES `youtubemd`.`VideoInformation` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
CHECKSUM = 1;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`DownloadStatistics`
-- Table `youtubemd`.`Metadata`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`DownloadStatistics` (
`timesRequested` INT NOT NULL DEFAULT 0,
`DownloadInformation_file_id` VARCHAR(50) NOT NULL,
PRIMARY KEY (`DownloadInformation_file_id`),
CONSTRAINT `fk_DownloadStatistics_DownloadInformation1`
FOREIGN KEY (`DownloadInformation_file_id`)
REFERENCES `youtubemd`.`DownloadInformation` (`file_id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
CREATE TABLE IF NOT EXISTS `youtubemd`.`Metadata` (
`idMetadata` INT NOT NULL AUTO_INCREMENT,
`title` VARCHAR(100) NOT NULL,
`artist` VARCHAR(60) NOT NULL,
`cover` BLOB NOT NULL,
`duration` INT NULL,
`customMetadata` TINYINT NOT NULL DEFAULT 0,
PRIMARY KEY (`idMetadata`))
ENGINE = InnoDB
AUTO_INCREMENT = 0
CHECKSUM = 1;
SHOW WARNINGS;
......@@ -246,6 +170,83 @@ CHECKSUM = 1;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`Preferences`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`Preferences` (
`language` VARCHAR(3) NOT NULL DEFAULT 'en',
`audioQuality` ENUM('320k', '256k', '128k') NOT NULL DEFAULT '128k',
`audioSampling` ENUM('44000', '48000') NOT NULL DEFAULT '44000',
`sendSongLinks` TINYINT NOT NULL DEFAULT 0,
`askForMetadata` TINYINT NOT NULL DEFAULT 1,
`User_id` INT(64) NOT NULL,
PRIMARY KEY (`User_id`),
CONSTRAINT `fk_Preferences_User`
FOREIGN KEY (`User_id`)
REFERENCES `youtubemd`.`User` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
CHECKSUM = 1;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`User`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`User` (
`id` INT(64) NOT NULL DEFAULT 0,
`name` VARCHAR(45) NULL DEFAULT 'User',
`surname` VARCHAR(45) NULL,
`username` VARCHAR(45) NULL,
`lastSeen` DATETIME NOT NULL,
`firstUsage` DATETIME NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
CHECKSUM = 1
PACK_KEYS = 1;
SHOW WARNINGS;
CREATE UNIQUE INDEX `id_UNIQUE` ON `youtubemd`.`User` (`id` ASC) VISIBLE;
SHOW WARNINGS;
CREATE UNIQUE INDEX `username_UNIQUE` ON `youtubemd`.`User` (`username` ASC) VISIBLE;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`VideoInformation`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`VideoInformation` (
`id` VARCHAR(11) NOT NULL,
`title` VARCHAR(100) NOT NULL,
`channel` VARCHAR(60) NOT NULL,
PRIMARY KEY (`id`))
ENGINE = InnoDB
CHECKSUM = 1;
SHOW WARNINGS;
CREATE UNIQUE INDEX `id_UNIQUE` ON `youtubemd`.`VideoInformation` (`id` ASC) VISIBLE;
SHOW WARNINGS;
-- -----------------------------------------------------
-- Table `youtubemd`.`VideoStatistics`
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS `youtubemd`.`VideoStatistics` (
`timesRequested` INT NOT NULL DEFAULT 0,
`VideoInformation_id` VARCHAR(11) NOT NULL,
PRIMARY KEY (`VideoInformation_id`),
CONSTRAINT `fk_VideoStatistics_VideoInformation1`
FOREIGN KEY (`VideoInformation_id`)
REFERENCES `youtubemd`.`VideoInformation` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB
CHECKSUM = 1;
SHOW WARNINGS;
SET SQL_MODE[email protected]OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS[email protected]OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS[email protected]OLD_UNIQUE_CHECKS;
This source diff could not be displayed because it is too large. You can view the blob instead.
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .bot import PROGRAM_ARGS
from .bot import main
from .logging_utils import LoggingHandler
from .logging_utils import setup_logging
from .decorators import send_action
from .decorators import restricted
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from telegram.ext import Updater
from telegram.ext import CommandHandler
PROGRAM_ARGS = None
def main(args: dict):
global PROGRAM_ARGS
PROGRAM_ARGS = args
updater = Updater(args["token"], workers=args["workers"])
dispatcher = updater.dispatcher
dispatcher.add_handler(CommandHandler("hello", main))
if args["use_webhook"]:
updater.start_webhook(listen=args["ip"],
port=args["port"],
url_path=args["token"],
webhook_url=args["public_url"] + '/' + args["token"])
else:
updater.start_polling(args["poll_interval"])
updater.idle()
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .. import LoggingHandler
class StartHandler(object):
def __init__(self):
self._user_data = {}
def start(self, bot, update):
self._user_data[]
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..constants.app_constants import ydl_cli_options
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
ydl_cli_options = ["youtube-dl", "--format", "bestaudio[ext=m4a]", "--quiet", "--output",
"-"]
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..decorators.decorators import restricted
from ..decorators.decorators import send_action
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from functools import wraps
from .. import PROGRAM_ARGS
# logging = LoggingHandler()
def send_action(action):
"""
Sends an action while processing a command.
:param action: the action to be performed.
:return: itself.
"""
def decorator(func):
@wraps(func)
def command_func(update, context, *args, **kwargs):
context.bot.send_chat_action(chat_id=update.effective_message.chat_id, action=action)
return func(update, context, *args, **kwargs)
return command_func
return decorator
def restricted(func):
"""
Restricts a specific function to be accessed from non-authorized users.
:param func: function to be wrapped.
:return: itself.
"""
@wraps(func)
def wrapped(update, context, *args, **kwargs):
user_id = update.effective_user.id
if user_id not in PROGRAM_ARGS["admin"]:
logging.warning("Unauthorized access denied for {}.".format(user_id))
return
return func(update, context, *args, **kwargs)
return wrapped
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..downloader.youtube_downloader import YouTubeDownloader
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from io import BytesIO
from typing import Tuple
from ..constants.app_constants import ydl_cli_options
class YouTubeDownloader(object):
def __init__(self, url: str):
self.__url: str = url
self.__options: list = ydl_cli_options.copy()
self.__options.append(self.__url)
def download(self) -> Tuple[BytesIO, bytes]:
import subprocess
proc = subprocess.Popen(self.__options,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
retcode = proc.returncode
if retcode == 0:
return BytesIO(stdout), stdout
else:
raise RuntimeError("youtube-dl downloader exception - more info: " +
str(stderr))
def get_url(self) -> str:
return self.__url
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from ..logging_utils.utils import LoggingHandler
from ..logging_utils.utils import setup_logging
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import logging
def cleanup_old_logs(log_file: str):
import tarfile
import os
tar_log_filename = log_file + ".tar.gz"
if os.path.exists(log_file):
if os.path.exists(tar_log_filename):
os.remove(tar_log_filename)
with tarfile.open(tar_log_filename, "w:gz") as tar:
tar.add(log_file, arcname=os.path.basename(log_file))
tar.close()
os.remove(log_file)
def setup_logging(logger_name: str, log_file: str, level=logging.DEBUG,
formatter: str = "%(process)d - %(asctime)s | [%(levelname)s]: %(message)s"):
from os import path
from os import makedirs
log_dir = path.dirname(path.abspath(log_file))
if not path.exists(log_dir):
makedirs(log_dir)
cleanup_old_logs(log_file)
new_logging = logging.getLogger(logger_name)
logging_formatter = logging.Formatter(formatter)
logging_file_handler = logging.FileHandler(log_file, mode="w")
logging_file_handler.setFormatter(logging_formatter)
new_logging.setLevel(level)
new_logging.addHandler(logging_file_handler)
return logging_file_handler
class LoggingHandler(object):
class __LoggingHandler(object):
def __init__(self, logs: list):
self.__logs = logs
def debug(self, msg):
for log in self.__logs:
log.debug(msg)
def info(self, msg):
for log in self.__logs:
log.info(msg)
def error(self, msg):
for log in self.__logs:
log.error(msg)
def warning(self, msg):
for log in self.__logs:
log.warning(msg)
def critical(self, msg):
for log in self.__logs:
log.critical(msg)
def get_loggers(self) -> list:
return self.__logs
__instance = None
def __new__(cls, *args, **kwargs):
if not LoggingHandler.__instance:
logs = kwargs.get("logs")
if not logs or len(logs) == 0:
raise AttributeError("At least kwarg \"log\" (a list of the loggers) must be provided")
LoggingHandler.__instance = LoggingHandler.__LoggingHandler(logs)
return LoggingHandler.__instance
def __getattr__(self, item):
return getattr(self.__instance, item)
def __setattr__(self, key, value):
return setattr(self.__instance, key, value)
def debug(self, msg):
self.__instance.debug(msg)
def info(self, msg):
self.__instance.info(msg)
def error(self, msg):
self.__instance.error(msg)
def warning(self, msg):
self.__instance.warning(msg)
def critical(self, msg):
self.__instance.critical(msg)
def get_loggers(self) -> list:
return self.__instance.get_loggers()
# YouTubeMDBot
# Copyright (C) 2019 - Javinator9889
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,