import asyncio
import html
import logging
import re
import threading
import time
from asyncio import BaseEventLoop
from html.parser import HTMLParser

# noinspection PyPackageRequirements
import discord
import emojis as emojis
# noinspection PyPackageRequirements
from discord import Message, TextChannel, Guild, Embed, Role

from core.chat_blob import ChatBlob
from core.command_param_types import Const
from core.db import DB
from core.decorators import instance, command, event, timerevent
from core.dict_object import DictObject
from core.logger import Logger
from core.lookup.character_service import CharacterService
from core.lookup.pork_service import PorkService
from core.message_hub_service import MessageHubService
from core.setting_service import SettingService
from core.setting_types import HiddenSettingType
from core.text import Text
from core.igncore import IgnCore
from core.util import Util
from modules.core.accounting.services.access_service import AccessService
from modules.core.accounting.services.account_service import AccountService
from modules.core.ban.ban_service import BanService
from modules.onlinebot.online.org_alias_controller import OrgAliasController


class MLStripper(HTMLParser):
    def error(self, message):
        pass

    def __init__(self):
        super().__init__()
        self.reset()
        self.strict = False
        self.convert_charrefs = True
        self.fed = []

    def handle_data(self, d):
        self.fed.append(d)

    def get_data(self):
        return "".join(self.fed)


# noinspection PyUnusedLocal
@instance()
class DiscordController:
    MESSAGE_SOURCE = "discord"

    def __init__(self):
        self.logger = Logger(__name__)
        logging.getLogger("discord.gateway").setLevel(logging.WARN)
        logging.getLogger("discord.client").setLevel(logging.WARN)
        intents = discord.Intents.all()
        self.client = discord.Client(intents=intents, chunk_guilds_at_startup=True)
        self.client.event(self.on_ready)
        self.client.event(self.on_member_join)
        self.client.event(self.on_member_remove)
        self.client.event(self.on_member_update)
        self.client.event(self.on_guild_role_delete)
        self.client.event(self.on_guild_role_create)
        self.client.event(self.on_guild_role_update)
        self.client.event(self.on_invite_create)
        self.client.event(self.on_invite_delete)
        self.client.event(self.on_message)
        self.guild = None
        self.channel = None
        self.thread = None
        self.invites = None
        self.discord_roles = DictObject(
            {"override": "Override",
             "admin": "Administrator",
             "council": "Council",
             "leader": "Raid Leader",
             "president": "President",
             "general": "General",
             "officer": "Officer",
             "member": "Member",
             "failed": "failed"})
        # logging.getLogger("discord").setLevel(logging.INFO)
        self.discord_queue = []
        self.ao_queue = []

    # noinspection PyAttributeOutsideInit
    def inject(self, registry):
        self.bot: IgnCore = registry.get_instance("bot")
        self.db: DB = registry.get_instance("db")
        self.text: Text = registry.get_instance("text")
        self.character_service: CharacterService = registry.get_instance("character_service")
        self.util: Util = registry.get_instance("util")
        self.access_service: AccessService = registry.get_instance("access_service")
        self.pork: PorkService = registry.get_instance("pork_service")
        self.org: PorkService = registry.get_instance("pork_service")
        self.relay_hub_service: MessageHubService = registry.get_instance("message_hub_service")
        self.setting_service: SettingService = registry.get_instance("setting_service")
        self.alias_controller: OrgAliasController = registry.get_instance("org_alias_controller")
        self.account_service: AccountService = registry.get_instance("account_service")
        self.setting_service: SettingService = registry.get_instance("setting_service")

    def pre_start(self):
        self.setting_service.register(self.module_name, "discord_token", "", HiddenSettingType(allow_empty=True),
                                          "Enter your Discord token here")

    def get_name(self, discord_id):
        data = self.db.query_single(
            "SELECT d.*, p.* FROM account d LEFT JOIN player p on d.main=p.char_id where discord_id=?", [discord_id])
        return data.name if data else None

    def setting_discord_token(self):
        return self.setting_service.get("discord_token")

    @command(command="discord", params=[Const("invite")], access_level="member",
             description="Get a personal Discord invite", sub_command="invite")
    def discord_invite_cmd(self, request, _):
        if not self.client:
            return "Discord module has not been initiated yet. Please try again later."
        account = self.account_service.get_account(request.sender.char_id)
        if account is None:
            return "You do not have an account"
        if account.disabled == 1:
            return "Your account is disabled"
        if account.discord_joined == 1:
            return "You have already joined the Discord server"
        if account.discord_invite != "":
            for ginvite in self.invites:
                if ginvite.code == account.discord_invite:
                    asyncio.run_coroutine_threadsafe(self.discord_delete_invite(ginvite), self.loop)
        invite = asyncio.run_coroutine_threadsafe(self.discord_create_invite(account.name), self.loop)
        invite = invite.result()
        self.set_discord_invite(account.char_id, invite.code)
        self.bot.send_mass_message(request.sender.char_id,
                                   f"Your personal Discord invite is: {invite} \n"
                                   f"This invite is only valid for 5 minutes.")

    @command(command="discord", params=[Const("update")], access_level="member",
             description="Update your discord information", sub_command="update")
    def discord_update_cmd(self, request, _):
        if not self.client:
            return "Discord module has not been initiated yet. Please try again later."
        return self.discord_update_account(request.sender.char_id)

    @command(command="discord", params=[Const("disconnect")], access_level="admin",
             description="Disconnect from Discord", sub_command="admin")
    def discord_disconnect_cmd(self, _, _1):
        if not self.client:
            return "Discord module has not been initiated yet. Please try again later."
        if self.client.is_closed():
            return "Discord is already disconnected"
        else:
            asyncio.run_coroutine_threadsafe(self.discord_disconnect(), self.loop)
            return "Disconnecting Discord"

    @command(command="discord",
             params=[Const("members")],
             access_level="admin",
             description="Get all discord members",
             sub_command="members")
    def discord_members_cmd(self, _, _1):
        if not self.client:
            return "Discord module has not been initiated yet. Please try again later."
        blob = ""
        for member in self.guild.members:
            member_roles = []
            for role in member.roles:
                if role.name == "@everyone":  # Skip @everyone as everyone has it.
                    continue
                member_roles.append("%s" % role.name)
            member_roles.sort(key=str.lower)
            blob += "%s (%s)\n" % (member.name + "#" + member.discriminator, ", ".join(member_roles))
        return ChatBlob("Discord Members (%d)" % (len(self.guild.members)), blob)

    @event(event_type="connect", description="Connects the Discord client automatically on startup, if a token exists")
    def handle_connect_event(self, _, _1):
        token = self.setting_discord_token().get_value()
        if token.lower() in ["none", "", "null"]:
            return

        # noinspection PyTypeChecker
        self.loop: BaseEventLoop = asyncio.get_event_loop()
        self.loop.create_task(self.discord_connect(token))
        self.thread = threading.Thread(target=self.run_it_forever, daemon=True)
        self.thread.start()

    @event(event_type=BanService.BAN_ADDED_EVENT, description="Ban user from Discord")
    def ban_added_event(self, _, event_data):
        token = self.setting_discord_token().get_value()
        if token.lower() in ["none", "", "null"]:
            return
        account = self.account_service.get_account(event_data.char_id)
        if account.discord_joined == 1 and account.discord_id != "":
            member = self.guild.get_member(int(account.discord_id))
            if member is not None:
                ban = asyncio.run_coroutine_threadsafe(self.discord_ban_user(member, event_data.reason), self.loop)

    @event(event_type=BanService.BAN_REMOVED_EVENT, description="Remove Discord ban")
    def ban_removed_event(self, _, event_data):
        token = self.setting_discord_token().get_value()
        if token.lower() in ["none", "", "null"]:
            return
        account = self.account_service.get_account(event_data.char_id)
        if account.discord_id != "":
            banlist = asyncio.run_coroutine_threadsafe(self.discord_banlist(), self.loop)
            banlist = banlist.result()
            for ban in banlist:
                if ban.user.id == int(account.discord_id):
                    unban = asyncio.run_coroutine_threadsafe(self.discord_unban_user(ban.user), self.loop)

    @timerevent(budatime="1s", description="Handle Discord queue")
    def timer_check_discord_queue(self, _, _1):
        while self.discord_queue:
            t = int(time.time())
            obj = self.discord_queue.pop(0)
            if obj.type == "on_member_remove":
                member = obj.member
                handle = member.name + "#" + member.discriminator

                account = self.get_account_discord_id(member.id)
                if not account:
                    log = '**%s** has left discord (**%s**)' % (member.nick or member.name, handle)
                    self.relay_hub_service.send_message("system_logger", 0, log, log)
                    self.logger.info(log)
                    continue
                main = self.account_service.get_main(account.char_id)
                if account is None:
                    continue
                log = '**%s** has left discord (**%s**)' % (main.name, handle)
                self.relay_hub_service.send_message("system_logger", 0, log, log)
                self.logger.info(log)
                # self.bot.send_private_channel_message("%s has left Discord (%s)" % (main.name, handle))
                self.set_discord_left(account.main)
                if account.discord_handle != handle:
                    self.set_discord_handle(member.id, handle)
            if obj.type == "on_member_join":
                member = obj.member
                invite_used = obj.invite
                handle = member.name + "#" + member.discriminator
                if invite_used is None:
                    log = '**%s** joined discord with unknown invite' % handle
                    self.relay_hub_service. \
                        send_message("system_logger", 0,
                                     log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
                                           f"check that!",
                                     log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
                                           f"check that!")
                    self.logger.info(log)
                    continue
                self.guild: Guild = self.client.get_guild(self.client.guilds[0].id)
                account = self.get_discord_invite(invite_used.code)
                if account is None:
                    log = '**%s** joined discord with invite **%s** but couldnt find account!' % (
                        handle, invite_used.code)
                    self.relay_hub_service. \
                        send_message("system_logger", 0,
                                     log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
                                           f"check that!",
                                     log + f" {self.get_role('Administrator', self.guild.roles).mention}'s, "
                                           f"check that!")
                    self.logger.info(log)
                    asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, None, None), self.loop)
                    continue

                main = self.account_service.get_main(account.main)
                self.set_discord_joined(account.main, handle, member.id)
                if self.setting_service.get_value('is_alliance_bot') == '1':
                    nick = f"{f'[{self.alias_controller.get_alias(main.org_id)}] '}{main.name}"
                else:
                    nick = f"{main.name}"
                nick = asyncio.run_coroutine_threadsafe(self.discord_member_nick(member, nick), self.loop)
                access_level = access_level = self.access_service.get_access_level(account.main)
                roles = asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, account, access_level),
                                                         self.loop)
                # noinspection LongLine
                log = '**%s** joined discord with invite **%s** and matches account **%s**' \
                      % (handle, invite_used.code,
                         f"{f'[{self.alias_controller.get_alias(main.org_id)}] ' if self.setting_service.get_value('is_alliance_bot') == '1' else ''}{main.name}")
                self.relay_hub_service.send_message("system_logger", account.main, log, log)
                self.logger.info(log)

    @timerevent(budatime="1h", description="Verify Discord members", run_at_startup=True)
    def timer_check_discord_members(self, event_type, event_data):
        token = self.setting_discord_token().get_value()
        if token.lower() in ["none", "", "null"]:
            return
        if not self.bot.is_ready():
            return
        update = asyncio.run_coroutine_threadsafe(self.discord_update_bot_basic(), self.loop)
        # Update accounts that have left/joined discord without the bot being online
        accounts = self.db.query("SELECT * FROM account WHERE discord_id !=''")
        for account in accounts:
            match = False
            for member in self.guild.members:
                handle = member.name + "#" + member.discriminator
                if member.id == int(account.discord_id):
                    match = True
                    if account.discord_joined == 0:
                        self.set_discord_joined(account.main, handle, member.id)
                    break
            if match is False:
                if account.discord_joined == 1:
                    self.set_discord_left(account.main)
        # Update current discord Members
        accounts = self.db.query("SELECT * FROM account WHERE discord_joined = 1 and char_id = main")
        for member in self.guild.members:
            if member.id == self.client.user.id:
                continue
            member_account = None

            for account in list(accounts):
                if int(account.discord_id) == member.id:
                    member_account = account
                    accounts.remove(account)
                    break
            access_level = 0
            if member_account is not None:
                access_level = self.access_service.get_access_level(member_account.main)
            roles = asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, member_account, access_level),
                                                     self.loop)
            if member_account is not None:
                main = self.pork.get_character_info(member_account.main)
                # noinspection LongLine
                nick = f"{f'[{self.alias_controller.get_alias(main.org_id)}] ' if self.setting_service.get_value('is_alliance_bot') == '1' else ''}{main.name}"

                nick = asyncio.run_coroutine_threadsafe(self.discord_member_nick(member, nick), self.loop)

    def run_it_forever(self):
        self.loop.run_forever()

    def set_discord_invite(self, char_id, invite):
        return self.db.exec("UPDATE account SET discord_invite = ? WHERE main = ?", [invite, char_id])

    def set_discord_joined(self, char_id, handle, discord_id):
        return self.db.exec("UPDATE account SET discord_joined = 1, discord_handle = ?, discord_id = ? WHERE main = ?",
                            [handle, discord_id, char_id])

    def set_discord_left(self, char_id):
        return self.db.exec("UPDATE account SET discord_joined = 0 WHERE main = ?", [char_id])

    def set_discord_handle(self, discord_id, handle):
        return self.db.exec("UPDATE account SET discord_handle = ? WHERE discord_id = ?", [handle, discord_id])

    def get_discord_invite(self, invite):
        return self.db.query_single("SELECT * FROM account WHERE discord_invite = ?", [invite])

    def get_account_discord_id(self, discord_id):
        return self.db.query_single(
            "SELECT * FROM account a left join player p on a.char_id=p.char_id WHERE discord_id = ?", [discord_id])

    def discord_update_account(self, char_id):
        account = self.account_service.get_account(char_id)
        if account is None:
            return
        if account.discord_id != "" and account.discord_joined == 1:
            member = self.guild.get_member(int(account.discord_id))
            if member is not None:
                access_level = self.access_service.get_access_level(account.main)
                roles = asyncio.run_coroutine_threadsafe(self.discord_member_roles(member, account, access_level),
                                                         self.loop)

                # noinspection LongLine
                nick = f"{f'[{self.alias_controller.get_alias(account.org_id)}] ' if self.setting_service.get_value('is_alliance_bot') == '1' else ''}{account.name}"
                nick = asyncio.run_coroutine_threadsafe(self.discord_member_nick(member, nick), self.loop)
                return "Processing..."
        return "No Discord account found"

    def discord_invite_used(self, temp_invites):
        for temp_invite in temp_invites:
            # if temp_invite.inviter.id == self.client.user.id:
            # ## Using this line limits it to invite created by the account the Discord bot runs as..
            for invite in self.invites:
                if int(temp_invite.uses) > int(invite.uses) and temp_invite.code == invite.code:
                    return temp_invite
        return None

    def get_role(self, name, roles) -> Role or None:
        for role in roles:
            if role.name == name:
                return role
        return None

    async def on_member_join(self, member):
        temp_invites = await self.guild.invites()
        invite_used = self.discord_invite_used(temp_invites)
        self.discord_queue.append(DictObject({"type": "on_member_join", "member": member, "invite": invite_used}))
        await invite_used.delete()

    async def on_member_remove(self, member):
        self.discord_queue.append(DictObject({"type": "on_member_remove", "member": member}))
        await self.discord_update_bot_full()

    async def on_ready(self):
        await self.discord_update_bot_full()
        count = self.db.query_single('SELECT count(*) as count from online '
                                     'where char_id NOT IN (select char_id from org_bots) and bot=?',
                                     [self.bot.get_char_id()]).count

        act = discord.Activity(type=discord.ActivityType.listening,
                               name=f"{count} Players")
        asyncio.run_coroutine_threadsafe(self.client.change_presence(activity=act), self.loop)
        await self.clean_channel()

        if self.guild.large:
            self.logger.error(
                f"Guild {self.guild.name} is classified as large, "
                f"you need to request offline members to manage roles properly, this is not yet implemented")

    async def on_member_update(self, member_before, member_after):
        await self.discord_update_bot_basic()

    async def on_guild_role_create(self, role):
        await self.discord_update_bot_full()

    async def on_guild_role_delete(self, role):
        await self.discord_update_bot_full()

    async def on_guild_role_update(self, role_before, role_after):
        await self.discord_update_bot_full()

    async def on_invite_create(self, invite):
        await self.discord_update_bot_full()

    async def on_invite_delete(self, invite):
        await self.discord_update_bot_full()

    async def discord_update_bot_basic(self):
        self.guild = self.client.get_guild(self.client.guilds[0].id)
        self.channel = self.client.get_channel(self.guild.text_channels[0].id)

    async def discord_update_bot_full(self):
        await self.discord_update_bot_basic()
        self.invites = await self.guild.invites()

    async def discord_disconnect(self):
        await self.client.close()

    async def discord_connect(self, token):
        try:
            if not self.client.is_closed():
                self.logger.info("Logging into Discord...")
                await self.client.start(token)

        except discord.ClientException as exc:
            self.logger.error("Something broke, I'm out!: %s" % str(exc))

    async def discord_create_invite(self, reason=""):
        created_invite = await self.channel.create_invite(max_age=300, max_uses=2, reason=reason)
        self.guild = self.client.get_guild(self.client.guilds[0].id)
        return created_invite

    async def discord_delete_invite(self, invite):
        await invite.delete()

    async def discord_banlist(self):
        return await self.guild.bans()

    async def discord_ban_user(self, user, reason=""):
        await self.guild.ban(user, reason=reason)

    async def discord_unban_user(self, user):
        await self.guild.unban(user)

    async def discord_member_nick(self, account, nick):
        if self.get_role(self.discord_roles.override, account.roles):
            return  # We do not process accounts that have `override` role
        if account.nick != nick:
            result = await account.edit(nick=nick)

    async def discord_member_roles(self, discord_user: discord.Member, account, access_level):
        if self.get_role(self.discord_roles.override, discord_user.roles):
            return  # We do not process accounts that have `override` role
        addroles = []
        remroles = []
        if account is None:  # We assign failed role
            discrole = self.get_role(self.discord_roles.failed, discord_user.roles)
            self.logger.info("%s role result is %s" % (discord_user.nick, discrole))
            if discrole is None:
                failed = self.get_role(self.discord_roles.failed, self.guild.roles)
                if failed is None:
                    return
                addroles.append(failed)
                remroles = discord_user.roles
        else:
            # Superadmin
            if access_level["level"] in [10, 20]:
                if self.get_role(self.discord_roles.admin, discord_user.roles) is None:
                    rank = self.get_role(self.discord_roles.admin, self.guild.roles)
                    if rank is not None:
                        addroles.append(rank)
            else:
                rank = self.get_role(self.discord_roles.admin, discord_user.roles)
                if rank is not None:
                    remroles.append(rank)

            # Member Role
            if 100 > access_level["level"]:
                rank = self.get_role(self.discord_roles.member, self.guild.roles) or None
                if rank is not None:
                    addroles.append(rank)
            else:
                rank = self.get_role(self.discord_roles.member, self.guild.roles) or None
                if rank is not None:
                    remroles.append(rank)

            # Leader
            if self.account_service.check_leader(account.main):
                rank = self.get_role(self.discord_roles.leader, self.guild.roles) or None
                if rank is not None:
                    addroles.append(rank)
            else:
                rank = self.get_role(self.discord_roles.leader, self.guild.roles) or None
                if rank is not None:
                    remroles.append(rank)

            if self.setting_service.get_value("is_alliance_bot") == "1":

                if self.account_service.check_council(account.main):
                    rank = self.get_role(self.discord_roles.council, self.guild.roles) or None
                    if rank is not None:
                        addroles.append(rank)
                else:
                    rank = self.get_role(self.discord_roles.council, self.guild.roles) or None
                    if rank is not None:
                        remroles.append(rank)

                # President
                if self.account_service.check_president(account.main):
                    rank = self.get_role(self.discord_roles.president, self.guild.roles) or None
                    if rank is not None:
                        addroles.append(rank)
                else:
                    rank = self.get_role(self.discord_roles.president, self.guild.roles) or None
                    if rank is not None:
                        remroles.append(rank)

                # General
                if self.account_service.check_general(account.main):
                    rank = self.get_role(self.discord_roles.general, self.guild.roles) or None
                    if rank is not None:
                        addroles.append(rank)
                else:
                    rank = self.get_role(self.discord_roles.general, self.guild.roles) or None
                    if rank is not None:
                        remroles.append(rank)

                # Officer
                if self.account_service.check_officer(account.main):
                    rank = self.get_role(self.discord_roles.officer, self.guild.roles) or None
                    if rank is not None:
                        addroles.append(rank)
                else:
                    rank = self.get_role(self.discord_roles.officer, self.guild.roles) or None
                    if rank is not None:
                        remroles.append(rank)

            if account.disabled == 1 or access_level == 0 or access_level == 100:
                addroles = [self.get_role(self.discord_roles.failed, self.guild.roles)]
                remroles = discord_user.roles
            else:
                remroles.append(self.get_role(self.discord_roles.failed, self.guild.roles))
        # Execute
        if len(addroles) > 0:
            addroles = list(dict.fromkeys(addroles))  # Remove Duplicates
            for role in addroles:
                await discord_user.add_roles(role)
        if len(remroles) > 0:
            remroles = list(dict.fromkeys(remroles))  # Remove Duplicates
            for role in remroles:
                if role.name == "@everyone":  # Skip @everyone as we cant remove that
                    continue
                await discord_user.remove_roles(role)

    async def on_message(self, msg: Message):
        if msg.author.id == self.client.user.id:
            return
        channel: TextChannel = msg.channel
        if channel.id == int(self.setting_service.get_value("dc_relay_public")):
            if self.setting_service.get_value('is_alliance_bot') == "0":
                response = f"[<notice>{html.escape(msg.author.nick if msg.author.nick else msg.author.name, False)}" \
                           f"</notice>]: " \
                           f"{html.escape(emojis.decode(msg.clean_content), False)}"
            else:
                response = f"{html.escape(msg.author.nick if msg.author.nick else msg.author.name, False)}: " \
                           f"{html.escape(emojis.decode(msg.clean_content), False)}"
            await msg.delete(delay=3600)
            self.relay_hub_service.send_message("public_relay", [msg.author.nick, msg.author], response, response)

        if self.is_command(msg.content):
            admin = self.get_role("Administrator", self.guild.roles)
            council = self.get_role("Council", self.guild.roles)

            if msg.content[:4] == "!pin":
                if admin in msg.author.roles:
                    matches = re.findall(pattern="<head>(.+?)<\/head>(.+)", string=msg.content[4:].strip(),
                                         flags=re.DOTALL)
                    if matches:
                        mess = await channel.send(
                            embed=Embed(color=3066993, title=matches[0][0], description=matches[0][1]))
                    else:
                        mess = await channel.send(embed=Embed(color=3066993, description=msg.content[4:]))
                    await mess.pin()

            if msg.content[:5] == "!note":
                if admin in msg.author.roles:
                    matches = re.findall(pattern="<head>(.+?)<\/head>(.+)", string=msg.content[5:].strip(),
                                         flags=re.DOTALL)
                    if matches:
                        mess = await channel.send(
                            embed=Embed(color=3066993, title=matches[0][0], description=matches[0][1]))
                    else:
                        mess = await channel.send(embed=Embed(color=3066993, description=msg.content[5:]))

            if msg.content[:6] == "!purge":
                if admin in msg.author.roles:
                    count: str = msg.content[6:].strip()
                    if type(count) == str:
                        if count.isdigit():
                            # noinspection PyTypeChecker
                            count = int(count)
                    if type(count) == int:
                        # noinspection PyTypeChecker
                        while count > 100:
                            await msg.channel.purge(check=self.check)
                            count -= 100
                        else:
                            await msg.channel.purge(check=self.check, limit=count)
            await msg.delete(delay=0)
            if msg.content[:10] == "!subscribe":
                if admin in msg.author.roles:
                    out = msg.content[10:].strip()
                    if self.setting_service.get(out) is None:
                        await msg.reply(f"The Channel {out} does not exist.")
                    else:
                        self.setting_service.set_value(out, msg.channel.id)
                        self.send_message(msg.channel.id, Embed(color=3066993, title="Channel Guard",
                                                                description=f"This channel has been "
                                                                            f"subscriped to the source {out}."))

    def send_message(self, channel: int, message, delete=0):
        if self.client.is_ready():
            channel: TextChannel = self.client.get_channel(int(channel)) if type(channel) in [int, str] else channel
            if type(message) == Embed:
                if delete != 0:
                    asyncio.run_coroutine_threadsafe(channel.send(embed=message, delete_after=delete), self.loop)
                else:
                    asyncio.run_coroutine_threadsafe(channel.send(embed=message), self.loop)

            else:
                if delete != 0:
                    asyncio.run_coroutine_threadsafe(channel.send(message, delete_after=delete), self.loop)
                else:
                    asyncio.run_coroutine_threadsafe(channel.send(message), self.loop)

    def is_command(self, message: str):
        for x in ["subscribe", "pin", "purge", "note"]:
            if message.startswith("!" + x):
                return True

    async def clean_channel(self):
        if self.setting_service.get_value("dc_relay_public") not in ["0", None]:
            channel: TextChannel = self.client.get_channel(int(self.setting_service.get_value("dc_relay_public")))
            for i in range(5):
                await channel.purge(check=self.check)

    def check(self, msg: Message):
        if msg.pinned or len(msg.embeds) > 0:
            return False
        else:
            return True

    @event(event_type="main_changed", description="Fix discord names & ranks")
    def fix_ranks(self, _, data):
        row = self.db.query_single("SELECT * from account where char_id=?", [data.old_main_id])
        if row.discord_joined == 0:
            self.logger.debug(f"{data.old_main_id} was not in discord, ignoring main fixing")
            return
        elif row.discord_joined == 1:
            self.db.exec(
                "UPDATE account set discord_handle=?, discord_id=?, discord_invite=?, discord_joined=? where char_id=?",
                [row.discord_handle, row.discord_id, row.discord_invite, row.discord_joined, data.new_main_id])
            self.db.exec(
                "UPDATE account set discord_handle='', discord_id=0, discord_invite='', discord_joined=0 "
                "where char_id=?",
                [data.old_main_id])
            self.logger.info(f"{data.old_main_id} was in discord, overwriting {data.new_main_id} with {data}")

            self.discord_update_account(data.new_main_id)

    @timerevent(budatime="5m", description="update activity")
    def change_count(self, _, _1):
        if hasattr(self, "loop"):
            count = self.db.query_single('SELECT count(*) as count from online '
                                         'where char_id NOT IN (select char_id from org_bots) and bot=?',
                                         [self.bot.get_char_id()]).count
            act = discord.Activity(type=discord.ActivityType.listening,
                                   name=f"{count} Players")
            asyncio.run_coroutine_threadsafe(self.client.change_presence(activity=act), self.loop)
