343 lines
12 KiB
Python
343 lines
12 KiB
Python
import logging
|
|
import os
|
|
import platform
|
|
import queue
|
|
import pytz
|
|
import sys
|
|
from datetime import datetime
|
|
from logging.handlers import QueueHandler, QueueListener, TimedRotatingFileHandler
|
|
from pathlib import Path
|
|
|
|
import emoji
|
|
from colorama import Fore, init
|
|
|
|
from TwitchChannelPointsMiner.classes.Discord import Discord
|
|
from TwitchChannelPointsMiner.classes.Webhook import Webhook
|
|
from TwitchChannelPointsMiner.classes.Matrix import Matrix
|
|
from TwitchChannelPointsMiner.classes.Settings import Events
|
|
from TwitchChannelPointsMiner.classes.Telegram import Telegram
|
|
from TwitchChannelPointsMiner.classes.Pushover import Pushover
|
|
from TwitchChannelPointsMiner.utils import remove_emoji
|
|
|
|
|
|
# Fore: BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE, RESET.
|
|
class ColorPalette(object):
|
|
def __init__(self, **kwargs):
|
|
# Init with default values RESET for all and GREEN and RED only for WIN and LOSE bet
|
|
# Then set args from kwargs
|
|
for k in Events:
|
|
setattr(self, str(k), Fore.RESET)
|
|
setattr(self, "BET_WIN", Fore.GREEN)
|
|
setattr(self, "BET_LOSE", Fore.RED)
|
|
|
|
for k in kwargs:
|
|
if k.upper() in dir(self) and getattr(self, k.upper()) is not None:
|
|
if kwargs[k] in [
|
|
Fore.BLACK,
|
|
Fore.RED,
|
|
Fore.GREEN,
|
|
Fore.YELLOW,
|
|
Fore.BLUE,
|
|
Fore.MAGENTA,
|
|
Fore.CYAN,
|
|
Fore.WHITE,
|
|
Fore.RESET,
|
|
]:
|
|
setattr(self, k.upper(), kwargs[k])
|
|
elif kwargs[k].upper() in [
|
|
"BLACK",
|
|
"RED",
|
|
"GREEN",
|
|
"YELLOW",
|
|
"BLUE",
|
|
"MAGENTA",
|
|
"CYAN",
|
|
"WHITE",
|
|
"RESET",
|
|
]:
|
|
setattr(self, k.upper(), getattr(Fore, kwargs[k].upper()))
|
|
|
|
def get(self, key):
|
|
color = getattr(self, str(key)) if str(key) in dir(self) else None
|
|
return Fore.RESET if color is None else color
|
|
|
|
|
|
class LoggerSettings:
|
|
__slots__ = [
|
|
"save",
|
|
"less",
|
|
"console_level",
|
|
"console_username",
|
|
"time_zone",
|
|
"file_level",
|
|
"emoji",
|
|
"colored",
|
|
"color_palette",
|
|
"auto_clear",
|
|
"telegram",
|
|
"discord",
|
|
"webhook",
|
|
"matrix",
|
|
"pushover",
|
|
"username"
|
|
]
|
|
|
|
def __init__(
|
|
self,
|
|
save: bool = True,
|
|
less: bool = False,
|
|
console_level: int = logging.INFO,
|
|
console_username: bool = False,
|
|
time_zone: str or None = None,
|
|
file_level: int = logging.DEBUG,
|
|
emoji: bool = platform.system() != "Windows",
|
|
colored: bool = False,
|
|
color_palette: ColorPalette = ColorPalette(),
|
|
auto_clear: bool = True,
|
|
telegram: Telegram or None = None,
|
|
discord: Discord or None = None,
|
|
webhook: Webhook or None = None,
|
|
matrix: Matrix or None = None,
|
|
pushover: Pushover or None = None,
|
|
username: str or None = None
|
|
):
|
|
self.save = save
|
|
self.less = less
|
|
self.console_level = console_level
|
|
self.console_username = console_username
|
|
self.time_zone = time_zone
|
|
self.file_level = file_level
|
|
self.emoji = emoji
|
|
self.colored = colored
|
|
self.color_palette = color_palette
|
|
self.auto_clear = auto_clear
|
|
self.telegram = telegram
|
|
self.discord = discord
|
|
self.webhook = webhook
|
|
self.matrix = matrix
|
|
self.pushover = pushover
|
|
self.username = username
|
|
|
|
|
|
class FileFormatter(logging.Formatter):
|
|
def __init__(self, *, fmt, settings: LoggerSettings, datefmt=None):
|
|
self.settings = settings
|
|
self.timezone = None
|
|
if settings.time_zone:
|
|
try:
|
|
self.timezone = pytz.timezone(settings.time_zone)
|
|
logging.info(f"File logger time zone set to: {self.timezone}")
|
|
except pytz.UnknownTimeZoneError:
|
|
logging.error(
|
|
f"File logger: invalid time zone: {settings.time_zone}")
|
|
logging.Formatter.__init__(self, fmt=fmt, datefmt=datefmt)
|
|
|
|
def formatTime(self, record, datefmt=None):
|
|
if self.timezone:
|
|
dt = datetime.fromtimestamp(record.created, self.timezone)
|
|
else:
|
|
dt = datetime.fromtimestamp(record.created)
|
|
return dt.strftime(datefmt or self.default_time_format)
|
|
|
|
|
|
class GlobalFormatter(logging.Formatter):
|
|
def __init__(self, *, fmt, settings: LoggerSettings, datefmt=None):
|
|
self.settings = settings
|
|
self.timezone = None
|
|
if settings.time_zone:
|
|
try:
|
|
self.timezone = pytz.timezone(settings.time_zone)
|
|
logging.info(
|
|
f"Console logger time zone set to: {self.timezone}")
|
|
except pytz.UnknownTimeZoneError:
|
|
logging.error(
|
|
f"Console logger: invalid time zone: {settings.time_zone}")
|
|
logging.Formatter.__init__(self, fmt=fmt, datefmt=datefmt)
|
|
|
|
def formatTime(self, record, datefmt=None):
|
|
if self.timezone:
|
|
dt = datetime.fromtimestamp(record.created, self.timezone)
|
|
else:
|
|
dt = datetime.fromtimestamp(record.created)
|
|
return dt.strftime(datefmt or self.default_time_format)
|
|
|
|
def format(self, record):
|
|
record.emoji_is_present = (
|
|
record.emoji_is_present if hasattr(
|
|
record, "emoji_is_present") else False
|
|
)
|
|
if (
|
|
hasattr(record, "emoji")
|
|
and self.settings.emoji is True
|
|
and record.emoji_is_present is False
|
|
):
|
|
record.msg = emoji.emojize(
|
|
f"{record.emoji} {record.msg.strip()}", language="alias"
|
|
)
|
|
record.emoji_is_present = True
|
|
|
|
if self.settings.emoji is False:
|
|
if "\u2192" in record.msg:
|
|
record.msg = record.msg.replace("\u2192", "-->")
|
|
|
|
# With the update of Stream class, the Stream Title may contain emoji
|
|
# Full remove using a method from utils.
|
|
record.msg = remove_emoji(record.msg)
|
|
|
|
record.msg = self.settings.username + record.msg
|
|
|
|
if hasattr(record, "event"):
|
|
self.telegram(record)
|
|
self.discord(record)
|
|
self.webhook(record)
|
|
self.matrix(record)
|
|
self.pushover(record)
|
|
|
|
if self.settings.colored is True:
|
|
record.msg = (
|
|
f"{self.settings.color_palette.get(record.event)}{record.msg}"
|
|
)
|
|
|
|
return super().format(record)
|
|
|
|
def telegram(self, record):
|
|
skip_telegram = False if hasattr(
|
|
record, "skip_telegram") is False else True
|
|
|
|
if (
|
|
self.settings.telegram is not None
|
|
and skip_telegram is False
|
|
and self.settings.telegram.chat_id != 123456789
|
|
):
|
|
self.settings.telegram.send(record.msg, record.event)
|
|
|
|
def discord(self, record):
|
|
skip_discord = False if hasattr(
|
|
record, "skip_discord") is False else True
|
|
|
|
if (
|
|
self.settings.discord is not None
|
|
and skip_discord is False
|
|
and self.settings.discord.webhook_api
|
|
!= "https://discord.com/api/webhooks/0123456789/0a1B2c3D4e5F6g7H8i9J"
|
|
):
|
|
self.settings.discord.send(record.msg, record.event)
|
|
|
|
def webhook(self, record):
|
|
skip_webhook = False if hasattr(
|
|
record, "skip_webhook") is False else True
|
|
|
|
if (
|
|
self.settings.webhook is not None
|
|
and skip_webhook is False
|
|
and self.settings.webhook.endpoint
|
|
!= "https://example.com/webhook"
|
|
):
|
|
self.settings.webhook.send(record.msg, record.event)
|
|
|
|
def matrix(self, record):
|
|
skip_matrix = False if hasattr(
|
|
record, "skip_matrix") is False else True
|
|
|
|
if (
|
|
self.settings.matrix is not None
|
|
and skip_matrix is False
|
|
and self.settings.matrix.room_id != "..."
|
|
and self.settings.matrix.access_token
|
|
):
|
|
self.settings.matrix.send(record.msg, record.event)
|
|
|
|
def pushover(self, record):
|
|
skip_pushover = False if hasattr(
|
|
record, "skip_pushover") is False else True
|
|
|
|
if (
|
|
self.settings.pushover is not None
|
|
and skip_pushover is False
|
|
and self.settings.pushover.userkey != "YOUR-ACCOUNT-TOKEN"
|
|
and self.settings.pushover.token != "YOUR-APPLICATION-TOKEN"
|
|
):
|
|
self.settings.pushover.send(record.msg, record.event)
|
|
|
|
|
|
def configure_loggers(username, settings):
|
|
if settings.colored is True:
|
|
init(autoreset=True)
|
|
|
|
# Queue handler that will handle the logger queue
|
|
logger_queue = queue.Queue(-1)
|
|
queue_handler = QueueHandler(logger_queue)
|
|
root_logger = logging.getLogger()
|
|
root_logger.setLevel(logging.DEBUG)
|
|
# Add the queue handler to the root logger
|
|
# Send log messages to another thread through the queue
|
|
root_logger.addHandler(queue_handler)
|
|
|
|
# Adding a username to the format based on settings
|
|
console_username = "" if settings.console_username is False else f"[{username}] "
|
|
|
|
settings.username = console_username
|
|
|
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
console_handler.setLevel(settings.console_level)
|
|
console_handler.setFormatter(
|
|
GlobalFormatter(
|
|
fmt=(
|
|
"%(asctime)s - %(levelname)s - [%(funcName)s]: %(message)s"
|
|
if settings.less is False
|
|
else "%(asctime)s - %(message)s"
|
|
),
|
|
datefmt=(
|
|
"%d/%m/%y %H:%M:%S" if settings.less is False else "%d/%m %H:%M:%S"
|
|
),
|
|
settings=settings,
|
|
)
|
|
)
|
|
|
|
if settings.save is True:
|
|
logs_path = os.path.join(Path().absolute(), "logs")
|
|
Path(logs_path).mkdir(parents=True, exist_ok=True)
|
|
if settings.auto_clear is True:
|
|
logs_file = os.path.join(
|
|
logs_path,
|
|
f"{username}.log",
|
|
)
|
|
file_handler = TimedRotatingFileHandler(
|
|
logs_file,
|
|
when="D",
|
|
interval=1,
|
|
backupCount=7,
|
|
encoding="utf-8",
|
|
delay=False,
|
|
)
|
|
else:
|
|
# Getting time zone from the console_handler's formatter since they are the same
|
|
tz = "" if console_handler.formatter.timezone is False else console_handler.formatter.timezone
|
|
logs_file = os.path.join(
|
|
logs_path,
|
|
f"{username}.{datetime.now(tz).strftime('%Y%m%d-%H%M%S')}.log",
|
|
)
|
|
file_handler = logging.FileHandler(logs_file, "w", "utf-8")
|
|
|
|
file_handler.setFormatter(
|
|
FileFormatter(
|
|
fmt="%(asctime)s - %(levelname)s - %(name)s - [%(funcName)s]: %(message)s",
|
|
datefmt="%d/%m/%y %H:%M:%S",
|
|
settings=settings
|
|
)
|
|
)
|
|
file_handler.setLevel(settings.file_level)
|
|
|
|
# Add logger handlers to the logger queue and start the process
|
|
queue_listener = QueueListener(
|
|
logger_queue, file_handler, console_handler, respect_handler_level=True
|
|
)
|
|
queue_listener.start()
|
|
return logs_file, queue_listener
|
|
else:
|
|
queue_listener = QueueListener(
|
|
logger_queue, console_handler, respect_handler_level=True
|
|
)
|
|
queue_listener.start()
|
|
return None, queue_listener
|