Files
0815Cracky ff22f47b90 update
2024-02-27 11:46:37 +01:00

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