text: # coding=utf-8
  import os
  import re
  import threading
  import asyncio
  import base64
  import json
  import importlib
  import random
  import time
  
  import discord
  from discord.ext import commands
  
  from core import updates
  from core.log_manager import logger
  from core.lang_manager import LangManager
  from core.config_manager import ConfigManager
  from core.module_manager import db, Game
  
  
  class Loader:
  
      imported_modules = []
      ascii_art = r"""                _                        _   _ _  __
       /\        | |                      | | (_) |/ /
      /  \  _   _| |_ ___  _ __ ___   __ _| |_ _| ' /
     / /\ \| | | | __/ _ \| '_ ` _ \ / _` | __| |  <
    / ____ \ |_| | || (_) | | | | | | (_| | |_| | . \
   /_/    \_\__,_|\__\___/|_| |_| |_|\__,_|\__|_|_|\_\  {}
                                                 """
  
      @staticmethod
      def print_ascii_art():
          print(Loader.ascii_art.format(Client.VERSION))
          time.sleep(0.1)
  
      @staticmethod
      def start():
          """Adds the ascii art and requests the token at the start of the program."""
          Client.clear_console()
          Loader.print_ascii_art()
          token = Loader.load_token()
          Client.clear_console()
          Loader.print_ascii_art()
          logger.info("Loading...")
          return token
  
      @staticmethod
      def load_token():
          if "Secret Token.json" not in os.listdir("."):  # Requests token and writes It in "Secret Token.json"
              with open("Secret Token.json", "w") as token_file:
                  logger.info("Please introduce your bot's secret token: ")
                  token = base64.b64encode(input().encode("utf-8")).decode("utf-8")  # Encodes token
                  json.dump({"secret_token": token}, token_file, indent=2)
          else:  # Loads token from file
              with open("Secret Token.json") as token_file:
                  try:
                      token = json.load(token_file)["secret_token"]
                  except json.decoder.JSONDecodeError:  # Avoids exception when closing without providing a valid token
                      token_file.close()
                      os.remove("Secret Token.json")
                      token = Loader.load_token()
          return token
  
      @staticmethod
      def load_modules():
          """Imports and instances all the modules automagically."""
          modules = []
  
          for i in os.listdir("modules"):
              module_name = os.path.splitext(i)[0]
              module_extension = os.path.splitext(i)[1]
  
              if module_extension == ".py" and module_name != "__init__":
                  try:
                      # noinspection PyPep8Naming
                      imported_module = importlib.import_module(f"modules.{module_name}")
                      Klass = getattr(imported_module, "Main")
                      Loader.imported_modules.append(imported_module)
                      modules.append(Klass())
                  except AttributeError:
                      logger.exception(f"Module '{module_name}' couldn't be loaded")
                  except:
                      logger.exception(f"Unexpected error while loading module {module_name}")
  
          logger.info(f"{len(modules)} modules loaded")
          return modules
  
      @staticmethod
      def reload_modules():
          """Reimports and instances all the modules automagically."""
          Loader.imported_modules = [importlib.reload(i) for i in Loader.imported_modules]  # Reimports the modules
          importlib.invalidate_caches()
          logger.info(f"{len(Loader.imported_modules)} modules loaded")
          return [getattr(i, "Main")() for i in Loader.imported_modules]  # Instances from the modules
  
  
  class Client(commands.Bot, LangManager, ConfigManager):
  
      VERSION = "v1.3.2"
  
      def __init__(self, command_prefix, self_bot, intents):
          commands.Bot.__init__(self, command_prefix=command_prefix, self_bot=self_bot, intents=intents)
          LangManager.__init__(self, lang_dir="lang/")
          ConfigManager.__init__(self, config_name="config.json")
  
          self.main_loop = False  # Variable which starts or stops the main loop
          self.MODULES = None
          self.LOGO_URL = "https://raw.githubusercontent.com/Axyss/AutomatiK/master/docs/assets/ak_logo.png"
          self.AVATAR_URL = "https://avatars3.githubusercontent.com/u/55812692"
          self.remove_command("help")
          self.is_started = False
          self.init_commands()
  
      async def on_ready(self):
          if not self.is_started:  # Prevents the next lines from executing more than once when reconnecting
              self.load_resources()
  
              updater = updates.Updates(local_version=Client.VERSION, link="https://github.com/Axyss/AutomatiK/releases/")
              threading.Thread(target=updater.start_checking, daemon=True).start()  # Update checker
              threading.Thread(target=self.cli, daemon=True).start()
  
              await self.change_presence(status=discord.Status.online,  # Changes status to "online"
                                         activity=discord.Game("!mk help")  # Changes activity (playing)
                                         )
              logger.info(f"AutomatiK bot {Client.VERSION} now online")
              self.is_started = True
  
      @staticmethod
      def clear_console():
          local_os = os.name
          if local_os in ("nt", "dos"):
              os.system("cls")
          elif local_os in ("linux", "osx", "posix"):
              os.system("clear")
          else:
              print("\n" * 120)
  
      def cli(self):
          """Manages the console commands."""
          while True:
              cli_command = input("")
  
              if cli_command == "shutdown":
                  self.main_loop = False
                  logger.info("Stopping...")
                  os._exit(1)
              else:
                  logger.error("Unknown terminal command. Use 'shutdown' to stop the bot.")
  
      def load_resources(self, reload=False):
          """Loads configuration, modules and language packages."""
          self.MODULES = Loader.reload_modules() if reload else Loader.load_modules()
          self.load_config()
          self.create_config_keys(self.MODULES)
          self.check_config_changes()
          self.load_lang(self.data_config["lang"])
          return True
  
      def get_status(self, service):
          """Translates boolean values to the strings showed in '!mk status'."""
          return self.lang["status_active"] if self.data_config[service] else self.lang["status_inactive"]
  
      def generate_message(self, title, link):
          """Generates the messages for the free games."""
          draft = random.choice(self.lang["generic_messages"]).format(title, link)
          if self.data_config["mention_status"]:  # Adds the mention_status value from config
              draft = self.data_config["mentioned_role"] + " " + draft
  
          return draft
  
      async def on_command_error(self, ctx, error):  # The second parameter is the error's information
          """Method used for error handling regarding the discord.py library."""
          if isinstance(error, discord.ext.commands.MissingPermissions):
              await ctx.channel.send(self.lang["missing_permissions"])
  
          elif isinstance(error, discord.ext.commands.errors.NoPrivateMessage):
              pass
  
          elif isinstance(error, discord.ext.commands.errors.CommandNotFound):
              await ctx.channel.send(self.lang["invalid_command"])
  
          elif isinstance(error, discord.ext.commands.errors.CommandOnCooldown):
              await ctx.channel.send(self.lang["cooldown_error"].format(int(error.retry_after)))
          else:
              try:
                  raise error
              except:
                  logger.exception("Unexpected error")
  
      def init_commands(self):
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(3, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def notify(ctx, *args):
              """Command to notify free games from non-supported platforms."""
              args = list(args)
  
              # Stores the link in another variable and removes It from args
              link = args[-1]
              args.pop()
  
              game_name = " ".join(args)  # Converts list to string with spaces between elements
              await ctx.channel.purge(limit=1)
              await ctx.channel.send(self.generate_message(game_name, link)
                                     + self.lang["notify_thanks"].format(ctx.author.id)  # Adds politeness
                                     )
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def mention(ctx, role_id):
              """Manages the mentions of the bot's messages."""
              if re.search("^<@&\w", role_id) and re.search(">$", role_id):
                  # If the string follows the std structure of a role <@&1234>
                  self.edit_config("mentioned_role", role_id)
                  await ctx.channel.send(self.lang["mention_established"])
                  logger.info(f"AutomatiK will now mention '{role_id}'")
              else:
                  await ctx.channel.send(self.lang["mention_error"])
  
          @self.command(aliases=["help"])
          @commands.guild_only()
          @commands.cooldown(1, 60, commands.BucketType.user)
          async def helpme(ctx):
              """Help command that uses embeds."""
              embed_help = discord.Embed(title=f"AutomatiK {Client.VERSION} Help ",
                                         description=self.lang["embed_description"],
                                         color=0x00BFFF
                                         )
              embed_help.set_footer(text=self.lang["embed_footer"],
                                    icon_url=self.AVATAR_URL
                                    )
              embed_help.set_thumbnail(url=self.LOGO_URL)
  
              embed_help.add_field(name=self.lang["embed_field1_name"],
                                   value="".join(self.lang["embed_field1_value"]), inline=False
                                   )
              embed_help.add_field(name=u"\U0001F6E0 " + self.lang["embed_field2_name"],
                                   value="".join(self.lang["embed_field2_value"]),
                                   inline=False)
  
              await ctx.channel.send(embed=embed_help)
              logger.debug(f"Command '{ctx.command}' invoked by {ctx.author}")
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def start(ctx):
              """Starts the AutomatiK service."""
              if self.main_loop:  # If service already started
                  await ctx.channel.send(self.lang["start_already"])
                  return None
  
              self.main_loop = True  # Changes It to True so the main loop can start
              await ctx.channel.send(self.lang["start_success"].format("<#" + str(ctx.channel.id) + ">"))
              logger.info(f"Main service was started on #{ctx.channel} by {str(ctx.author)}")
  
              while self.main_loop:  # MAIN LOOP
                  for i in self.MODULES:
                      if not self.data_config[f"{i.MODULE_ID}_status"]:  # Prevents games from getting added to the db
                          continue                                       # when a module isn't enabled
                      free_games = i.get_free_games()
                      # If there is at least one element this will put It in a list
                      free_games = [free_games] if isinstance(free_games, Game) else free_games
                      if free_games:
                          try:  # Checks if module author specified a threshold
                              i.THRESHOLD
                          except AttributeError:
                              i.THRESHOLD = 6
                          free_games = db.check_database(f"{i.MODULE_ID.upper()}_TABLE", free_games, int(i.THRESHOLD))
                          for j in free_games:
                              await ctx.channel.send(self.generate_message(j.name, j.link))
  
                  await asyncio.sleep(300)  # It will look for free games every 5 minutes
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def stop(ctx):
              """Stops the AutomatiK service"""
              if not self.main_loop:  # If service already stopped
                  await ctx.channel.send(self.lang["stop_already"])
                  return False
  
              self.main_loop = False  # Stops the loop by changing the boolean which maintains It active
              await ctx.channel.send(self.lang["stop_success"])
              logger.info(f"Main service was stopped by {str(ctx.author)}")
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          async def status(ctx):
              """Shows the bot's status."""
              embed_status = discord.Embed(title=self.lang["status"],
                                           description=self.lang["status_description"],
                                           color=0x00BFFF
                                           )
              embed_status.set_footer(text=self.lang["embed_footer"],
                                      icon_url=self.AVATAR_URL
                                      )
              embed_status.set_thumbnail(url=self.LOGO_URL)
              embed_status.add_field(name=self.lang["status_main"], value=self.lang["status_active"]
                                     if self.main_loop else self.lang["status_inactive"]
                                     )
  
              for i in self.MODULES:
                  embed_status.add_field(name=i.SERVICE_NAME,
                                         value=self.get_status(f"{i.MODULE_ID}_status")
                                         )
              await ctx.channel.send(embed=embed_status)
              logger.debug(f"Command '{ctx.command}' invoked by {ctx.author}")
  
          @self.command(aliases=["module"])
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def modules(ctx):
              """Shows information about the loaded modules."""
              module_ids = [i.MODULE_ID for i in self.MODULES]
              module_names = [i.SERVICE_NAME for i in self.MODULES]
              module_authors = [i.AUTHOR for i in self.MODULES]
  
              embed_module = discord.Embed(title=self.lang["modules"],
                                           description=self.lang["modules_description"],
                                           color=0x00BFFF
                                           )
              embed_module.set_footer(text=self.lang["embed_footer"],
                                      icon_url=self.AVATAR_URL
                                      )
              embed_module.set_thumbnail(url=self.LOGO_URL)
              embed_module.add_field(name="**ModuleID**", value="\n".join(module_ids))
              embed_module.add_field(name=self.lang["modules_service"], value="\n".join(module_names))
              embed_module.add_field(name=self.lang["modules_author"], value="\n".join(module_authors))
  
              await ctx.channel.send(embed=embed_module)
  
          @self.command(aliases=["disable"])
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def enable(ctx, service):
              """Global manager to enable/disable modules and some other services."""
              introduced_command = str(ctx.invoked_with)
              user_decision = True if introduced_command == "enable" else False
  
              for i in self.MODULES:
                  if service.lower() == i.MODULE_ID.lower():
                      self.edit_config(f"{i.MODULE_ID}_status", user_decision)
  
                      await ctx.channel.send(self.lang[f"module_{introduced_command}d"].format(service.capitalize()))
                      logger.info(f"{service.capitalize()} module {introduced_command}d by {ctx.author}")
                      return True
  
              await ctx.channel.send(self.lang[f"{introduced_command}_unknown"])
              return False
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def language(ctx, lang_code):
  
              if (lang_code + ".json") not in os.listdir("lang"):  # If the language .json does not exist
                  await ctx.channel.send(self.lang["language_error"])
  
              else:  # Edits the config value which contains what lang is going to be loaded
                  self.edit_config("lang", lang_code)
                  self.load_lang(self.data_config["lang"])  # Reloads the language
                  await ctx.channel.send(self.lang["language_changed"])
                  logger.info(f"Language changed to '{lang_code}' by {ctx.author}")
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def languages(ctx):
              lang_name, lang_author = self.get_lang_metadata()
              lang_ids = self.get_lang_ids()
  
              embed_langs = discord.Embed(title=self.lang["languages"],
                                          description=self.lang["languages_description"],
                                          color=0x00BFFF
                                          )
              embed_langs.set_footer(text=self.lang["embed_footer"],
                                     icon_url=self.AVATAR_URL
                                     )
              embed_langs.set_thumbnail(url=self.LOGO_URL)
              embed_langs.add_field(name="LangID", value="\n".join(lang_ids))
              embed_langs.add_field(name=self.lang["language"], value="\n".join(lang_name))
              embed_langs.add_field(name=self.lang["modules_author"], value="\n".join(lang_author))
  
              await ctx.channel.send(embed=embed_langs)
  
          @self.command()
          @commands.guild_only()
          @commands.cooldown(2, 10, commands.BucketType.user)
          @commands.has_permissions(administrator=True)
          async def reload(ctx):
              """Reloads configuration, modules and language packages."""
              logger.info("Reloading...")
              was_started = bool(self.main_loop)
              self.main_loop = False
              self.load_resources(reload=True)
              self.main_loop = was_started
  
              await ctx.channel.send(self.lang["reload_completed"])
              logger.info("Reload completed")
  
  
  if __name__ == "__main__":
  
      automatik = Client(command_prefix="!mk ", self_bot=False, intents=discord.Intents.default())
      try:
          automatik.run(base64.b64decode(Loader.start().encode("utf-8")).decode("utf-8"))
  
      except discord.errors.LoginFailure:
          logger.error("Invalid token, please make sure It's valid. Press enter to exit...")
          os.remove("Secret Token.json")
          input()
  