text: #!/usr/bin/python
  
  import discord
  from discord.ext import commands
  import logging
  import requests
  import datetime
  import random
  import pymysql
  import re
  import string
  
  # Bot Config
  import bot_config
  
  bot = commands.Bot(command_prefix='.', description='''Backers Verification Bot''')
  
  
  def main():
      logging.basicConfig(filename='{0}{1:%Y%m%d%H%M%S}-BackersBot-Discord.out'.format(bot_config.log_folder,
                                                                                      datetime.datetime.now()),
                          level=logging.INFO,
                          format='%(asctime)s: %(levelname)s: %(message)s',
                          datefmt='%Y/%m/%d-%H:%M:%S')
  
      bot.remove_command('help')
      bot.run(bot_config.discord_token)
  
  
  @bot.event
  async def on_ready():
      logging.info("Logged in as {0}-{1}".format(bot.user.name, bot.user.id))
  
      if bot_config.is_test:
          await bot.change_presence(status=discord.Status.invisible)
  
  
  # region Backer Roles
  @bot.command(pass_context=True)
  async def backer_help(ctx):
      log_command(ctx.message.author, "backer_help")
  
      msg = "This bot will help you identify yourself as a backer and obtain access to Discord's private channels.\r\r" \
            "In order to start the process, you'll need to know the email you've used to back our project. That would be " \
            "your Kickstarter email, PayPal email or your Facebook email if you have your Kickstarter and Facebook " \
            "accounts linked.\r\r" \
            "Send me the following command: \r\r" \
            ".backer_mail email@example.com"
      if ctx.message.channel.is_private:
          await bot.say(msg)
      else:
          await bot.delete_message(ctx.message)
          try:
              await bot.send_message(ctx.message.author, msg)
          except discord.errors.Forbidden:
              await bot.send_message(ctx.message.channel, "{0} you have disabled direct messages "
                                                          "from this server members. "
                                                          "Please, allow them temporarily so we can start the process."
                                     .format(ctx.message.author.mention))
  
  
  @bot.command(pass_context=True)
  async def backer_mail(ctx, email: str):
      log_command(ctx.message.author, "backer_mail", email)
  
      # Only works if we're on a private message
      if ctx.message.channel.is_private:
          # Check if email is valid
          if valid_email(email):
              # Check the Database and see if we have the email.
              # Also check it we already sent a verification code and send the same one
              mariadb = db_connect()
  
              try:
                  with mariadb.cursor() as cursor:
                      cursor.execute("SELECT `verification_code` FROM `backers` WHERE `email`=%s", email)
                      result = cursor.fetchone()
  
                      token = None
  
                      if result is None:
                          # User doesn't exists in the database. Throw an error.
                          await bot.say("The email address is not registered as a valid backer. "
                                        "Please, make sure you've entered the right email.\r\r")
                      elif result["verification_code"] is None:
                          # User hasn't started the verified proccess previously. Generate a new verifiy token.
                          token = generate_random_string(40)
  
                          # Save the token on the database.
                          cursor.execute("UPDATE `backers` SET `verification_code`=%s"
                                         " WHERE `email`=%s", (token, email))
                          mariadb.commit()
                      else:
                          # Get previous token and reuse it.
                          token = result["verification_code"]
  
                      if token is not None:
                          # Send an email with the token and say the instructions to verify it.
                          requests.post("https://api.mailgun.net/v2/{0}/messages".format(bot_config.mailgun_host),
                                        auth=("api", bot_config.mailgun_key),
                                        data={
                                            "from": "{0}".format(bot_config.mailgun_email),
                                            "to": email,
                                            "subject": "Discord: Email Verification",
                                            "html": "Hello Backer! <br/><br/>"
                                                    "This is a confirmation email to verify you as one of our "
                                                    "backers. In order to confirm you as a backer, please go to Discord "
                                                    "and send the following message to BackersBot: <br/><br/>"
                                                    ".backer_verify {0} {1}".format(email, token)
                                        })
  
                          await bot.say("Welcome backer! Just one more step to access the backer-exclusive channels. "
                                        "Please, check your email for the verification code we just sent you (please "
                                        "check your spam folder too just in case) and send "
                                        "me back the following command:\r\r"
                                        ".backer_verify {0} verification_code_here"
                                        .format(email))
              finally:
                  cursor.close()
                  mariadb.close()
          else:
              await bot.say("The email address looks like it's invalid. "
                            "Please, make sure you enter a valid email address.")
      else:
          await bot.delete_message(ctx.message)
          await bot.send_message(ctx.message.author, "That command only works on private message. "
                                                     "Please send me the command again.")
  
  
  @bot.command(pass_context=True)
  async def backer_verify(ctx, email: str, token: str):
      log_command(ctx.message.author, "backer_verify", email, token)
  
      # Only works if we're on a private message
      if ctx.message.channel.is_private:
          # Connect to the database and check if the email-token is correct
          mariadb = db_connect()
  
          try:
              with mariadb.cursor() as cursor:
                  cursor.execute("SELECT `discord_user_id`, `role_id` FROM `backers` WHERE `email`=%s"
                                 " AND `verification_code`=%s",
                                 (email, token))
                  result = cursor.fetchone()
  
                  if result is None:
                      # User doesn't exists in the database. Throw an error.
                      await bot.say("The combination of user and verification code doesn't exist. "
                                    "Please, make sure you've entered the right email and code.\r\r")
                  elif result["discord_user_id"] == ctx.message.author.id:
                      # The user is already registered
                      server = bot.get_server(id=bot_config.server_id)
                      server_member = discord.utils.get(server.members, id=ctx.message.author.id)
                      if server_member is not None:
                          server_role = discord.utils.get(server.roles, id=result["role_id"])
                          await bot.add_roles(server_member, server_role)
  
                      await bot.say("You've already been confirmed as a backer.")
                  elif result["discord_user_id"] is not None:
                      # Someone already registered this email.
                      await bot.say("It looks like this email has already been registered by another user.")
                  else:
                      # Check if the user has joined server
                      server = bot.get_server(id=bot_config.server_id)
                      server_member = discord.utils.get(server.members, id=ctx.message.author.id)
                      if server_member is not None:
                          # Update the database to register this user as taken
                          cursor.execute("UPDATE `backers` SET `discord_user_id`=%s"
                                         " WHERE `email`=%s AND `verification_code`=%s",
                                         (ctx.message.author.id, email, token))
                          mariadb.commit()
  
                          server_role = discord.utils.get(server.roles, id=result["role_id"])
  
                          await bot.add_roles(server_member, server_role)
                          await bot.say(
                              "Congratulations! You just completed the process and you've been confirmed as "
                              "a **{0}** tier backer. Now you have access to the private channels."
                              .format(server_role.name))
                      else:
                          await bot.say(
                              "You haven't joined our Discord server! You should join it first and then come "
                              "back and run the command again.\r\r"
                              "Please, join the server here: {0}".format(bot_config.server_invite_link))
          finally:
              cursor.close()
              mariadb.close()
      else:
          await bot.delete_message(ctx.message)
          await bot.send_message(ctx.message.author, "That command only works on private message. "
                                                     "Please send me the command again.")
  # endregion
  
  
  # region Util
  def log_command(author: discord.Member, command_name: str, *args):
      args_str = ' '.join(str(arg) for arg in args)
      if len(args_str) > 0: args_str = ' '+args_str
      logging.info("Processed command: {0}{1} by {2}".format(command_name, args_str, author.id))
  
  
  def check_user_role(author: discord.Member, rolecheck):
      if type(rolecheck) is list and len(set(rolecheck).intersection(set([role.id for role in author.roles]))) > 0:
          return True
  
      if type(rolecheck) is str and rolecheck in [role.id for role in author.roles]:
          return True
  
      return False
  
  
  def check_url(url):
      try:
          resp = requests.head(url)
      except requests.exceptions.MissingSchema:
          return False
      return resp.status_code < 400
  
  
  def valid_email(email):
      return re.match(r"(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email)
  
  
  def db_connect():
      # Connect to the database
      mariadb = pymysql.connect(host=bot_config.db_host,
                                port=bot_config.db_port,
                                user=bot_config.db_user,
                                password=bot_config.db_pass,
                                db=bot_config.db_name,
                                cursorclass=pymysql.cursors.DictCursor)
      return mariadb
  
  
  def generate_random_string(size=20, chars=string.ascii_uppercase + string.digits):
      return ''.join(random.choice(chars) for _ in range(size))
  # endregion
  
  
  if __name__ == '__main__':
      main()
  