UnoVRBot/main.py

162 lines
6.8 KiB
Python

# Copyright (C) 2022 nocturn9x
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyrogram import Client, idle
from pyrogram.session import Session
import asyncio
import logging
import time
import uvloop
import sys
from configparser import ConfigParser
from UnoVRBot.misc.config import BotConfig
from pydantic import ValidationError
# Main Bot module. This is the entry point to the Bot and it loads all the modules and
# performs automatic startup, actually pyrogram does all the job but shut up we are communist
def log(prefix: str = "", message: str = ""):
"""
Hey I like logging but this is used only when no proper
logging configuration is yet in place to actually be useful
:param message: The message to log to stderr, default to ""
:type message: str
:param prefix: Any extra prefix to add to the message, default to ""
Use {date} to replace it with the current date in d/m/Y format and
{time} to replace it with the current time in %H:%M:%S %p format or
just %T %p if you're a lazy sucker. A whitespace is automatically
added between the prefix and the suffix just in case you're dumb
:type prefix: str
:return: Nothing
:rtype: None
"""
sys.stderr.write(f"{prefix.format(date=time.strftime('%d/%m/%Y'), time=time.strftime('%T %p'))} {message}\n")
async def start(config: BotConfig):
"""
Starts the Bot, or at least tries to do so and give
up after a while cuz we've got other shit to do than starting shitty
Bots all day
:param config: The BotConfig class (yeah, thanks, explanation be like)
:type config: BotConfig
:return: Nothing, fucker
:rtype: None
"""
log("[Bot - INFO {date} {time}]",
"Loading an actual logging configuration")
logging.basicConfig(format=config.logging_format,
datefmt=config.logging_date_format,
level=config.logging_level)
logging.info("Now we're talking, creating the Client instance")
Session.notice_displayed = True # Sorry but no one really cares
logging.getLogger("pyrogram").setLevel(logging.WARNING) # Because pyrogram logs are fucking verbose
client = Client(api_id=config.api_id,
api_hash=config.api_hash,
bot_token=config.bot_token,
plugins={"root": config.plugins_dir},
workdir=config.working_directory,
name=config.session_name)
try:
await client.start()
logging.info("Client started, goin' asleep baby")
await idle()
except Exception as oh_no:
logging.error(f"A fatal error occurred -> {type(oh_no).__name__}: {oh_no}, exiting")
try:
logging.debug("Disconnecting from telegram")
await client.disconnect()
except Exception as disconnect:
logging.debug(f"Could not disconnect -> {type(disconnect).__name__}: {disconnect}")
finally:
if config.restart_times:
logging.warning(f"Restarting up to {config.restart_times} more times")
config.restart_times -= 1
await start(config)
def check_config(parser: ConfigParser):
"""
Checks if a parsed .INI file actually contains the required settings
or if the user is a sack of shit. Starts the Bot if the user
is actually a good boy or beat him to death otherwise :)
:param parser: A ConfigParser instance (I should really consider to become a teacher, boy)
:type parser: ConfigParser
:return: Again, not the slightest shit
:rtype: None
"""
getters = { # Sort of like a computed goto. Kind of, I just hate a ton of if conditions
"api_id": (ConfigParser.getint, "int"),
"api_hash": (ConfigParser.get, "str"),
"logging_format": (ConfigParser.get, "int"),
"logging_level": (ConfigParser.getint, "str"),
"logging_date_format": (ConfigParser.get, "str"),
"plugins_dir": (ConfigParser.get, "str"),
"working_directory": (ConfigParser.get, "str"),
"session_name": (ConfigParser.get, "str"),
"restart_times": (ConfigParser.getint, "int"),
"bot_token": (ConfigParser.get, "str")
}
options = {}
if "bot" not in parser:
log("[Bot - ERROR {date} {time}]",
"How am I gonna start if you can't even fucking fill a config file? Missing 'bot' section")
return
for option in parser["bot"]:
getter, expected = getters.get(option, (None, None))
if not getter:
log("[Bot - WARN {date} {time}]",
f"Unknown option '{option}', skipping it, idiot!")
continue
try:
options[option] = getter(parser, "bot", option)
except Exception as fucked_up:
log("[Bot - ERROR {date} {time}]",
f"Hey! Got a type mismatch for option '{option}' (expecting '{expected}'), can you please stop being a dumbass? -> {type(fucked_up).__name__}: {fucked_up}")
try:
asyncio.run(start(BotConfig(**options)))
except AttributeError: # Python 3.6
asyncio.get_event_loop().run_until_complete(start(BotConfig(**options)))
except ValidationError as validation_error:
log("[Bot - ERROR {date} {time}]",
f"What the heck did you do? Apparently some type mismatch occurred -> {validation_error}")
if __name__ == "__main__": # Start dat shit boi
uvloop.install()
parser = ConfigParser()
if len(sys.argv) < 2:
# Yeah yeah say what you want fucker, but how am I gonna log without proper config in place? :\
# Take this shitty print instead!
log("[Bot - INFO {date} {time}]",
"Loading config file at default path ('./config.ini'), were you too lazy to specify a custom one?")
file = "./config.ini"
else:
log("[Bot - INFO {date} {time}]",
f"I see you were not lazy! Loading config file at '{sys.argv[1]}'")
file = sys.argv[1]
try:
with open(file) as config:
parser.read_file(config)
except Exception as fuck: # Cause we're too lazy for anything else
log("[Bot - ERROR {date} {time}]",
f"Sometimes thing go well, sometimes they don't, fix this shit fucker -> {type(fuck.__name__)}: {fuck}")
else:
log("[Bot - INFO {date} {time}]",
"Config file loaded, let's see if you're retarded or not now, checking options")
check_config(parser)