Ejemplo n.º 1
0
 def __init__(self, bot: Bot) -> None:
     self.bot = bot
     self.temp_global_messages = dict()
     self.temp_server_messages = dict()
     self.temp_global_elo = dict()
     if not database_connect(bot):
         print("An error occured while connecting to the database")
         bot.close()
     self.task.start()
Ejemplo n.º 2
0

@bot.event
async def on_voice_state_update(member, before, after):
    guild = member.guild
    if not get(guild.roles, name=ROLE_NAME):
        await guild.create_role(name=ROLE_NAME)
    if not before.channel and after.channel:
        role = discord.utils.get(member.guild.roles, name=ROLE_NAME)
        await member.add_roles(role)
    elif before.channel and not after.channel:
        role = discord.utils.get(member.guild.roles, name=ROLE_NAME)
        await member.remove_roles(role)


@bot.command()
async def ping(ctx):
    await ctx.send('🏓 Pong! {0}ms'.format(round(bot.latency, 4) * 1000))


@bot.command()
async def pong(ctx):
    await ctx.send("🏓 You're doing this wrong!")


bot.run(BOT_TOKEN)

# does not appear to actually log out
print("Exiting process")
asyncio.run(bot.close())
Ejemplo n.º 3
0
class SimpleAsyncDiscord(threading.Thread):
    """
    SimpleAsyncDiscord client which is used to communicate to discord, and provides certain commands in the relay and
    triggered channels as well as private authentication to the bot to admin the server.
    """
    def __init__(self, version_information: str, logger: logging.Logger):
        """
        Constructor for the SimpleAsyncDiscord client the discord bot runs in.

        :param: version_information: the plugin's version_information string
        :param: logger: the logger used for logging, usually passed through from the minqlx plugin.
        """
        super().__init__()
        self.version_information: str = version_information
        self.logger: logging.Logger = logger
        self.discord: Optional[Bot] = None

        self.discord_bot_token: str = Plugin.get_cvar("qlx_discordBotToken")
        self.discord_application_id: str = Plugin.get_cvar(
            "qlx_discordApplicationId", str)
        self.discord_relay_channel_ids: set[int] = \
            SimpleAsyncDiscord.int_set(Plugin.get_cvar("qlx_discordRelayChannelIds", set))
        self.discord_relay_team_chat_channel_ids: set[
            int] = SimpleAsyncDiscord.int_set(
                Plugin.get_cvar("qlx_discordRelayTeamchatChannelIds", set))
        self.discord_triggered_channel_ids: set[
            int] = SimpleAsyncDiscord.int_set(
                Plugin.get_cvar("qlx_discordTriggeredChannelIds", set))
        self.discord_triggered_channel_message_prefix: str = Plugin.get_cvar(
            "qlx_discordTriggeredChatMessagePrefix")
        self.discord_command_prefix: str = Plugin.get_cvar(
            "qlx_discordCommandPrefix")
        self.discord_help_enabled: bool = Plugin.get_cvar(
            "qlx_discordEnableHelp", bool)
        self.discord_version_enabled: bool = Plugin.get_cvar(
            "qlx_discordEnableVersion", bool)
        self.discord_message_prefix: str = Plugin.get_cvar(
            "qlx_discordMessagePrefix")
        self.discord_show_relay_channel_names: bool = Plugin.get_cvar(
            "qlx_displayChannelForDiscordRelayChannels", bool)
        self.discord_replace_relayed_mentions: bool = \
            Plugin.get_cvar("qlx_discordReplaceMentionsForRelayedMessages", bool)
        self.discord_replace_triggered_mentions: bool = \
            Plugin.get_cvar("qlx_discordReplaceMentionsForTriggeredMessages", bool)

        extended_logging_enabled: bool = Plugin.get_cvar(
            "qlx_discordLogToSeparateLogfile", bool)
        if extended_logging_enabled:
            self.setup_extended_logger()

    @staticmethod
    def setup_extended_logger() -> None:
        discord_logger: logging.Logger = logging.getLogger("discord")
        discord_logger.setLevel(logging.DEBUG)
        # File
        file_path = os.path.join(minqlx.get_cvar("fs_homepath"),
                                 "minqlx_discord.log")
        maxlogs: int = minqlx.Plugin.get_cvar("qlx_logs", int)
        maxlogsize: int = minqlx.Plugin.get_cvar("qlx_logsSize", int)
        file_fmt: logging.Formatter = \
            logging.Formatter("(%(asctime)s) [%(levelname)s @ %(name)s.%(funcName)s] %(message)s", "%H:%M:%S")
        file_handler: logging.FileHandler = \
            RotatingFileHandler(file_path, encoding="utf-8", maxBytes=maxlogsize, backupCount=maxlogs)
        file_handler.setLevel(logging.DEBUG)
        file_handler.setFormatter(file_fmt)
        discord_logger.addHandler(file_handler)
        # Console
        console_fmt: logging.Formatter = \
            logging.Formatter("[%(name)s.%(funcName)s] %(levelname)s: %(message)s", "%H:%M:%S")
        console_handler: logging.Handler = logging.StreamHandler()
        console_handler.setLevel(logging.INFO)
        console_handler.setFormatter(console_fmt)
        discord_logger.addHandler(console_handler)

    @staticmethod
    def int_set(string_set: set[str]) -> set[int]:
        int_set = set()

        for item in string_set:
            if item == '':
                continue
            value = int(item)
            int_set.add(value)

        return int_set

    def status(self) -> str:
        if self.discord is None:
            return "No discord connection set up."

        if self.is_discord_logged_in():
            return "Discord connection up and running."

        return "Discord client not connected."

    def run(self) -> None:
        """
        Called when the SimpleAsyncDiscord thread is started. We will set up the bot here with the right commands, and
        run the discord.py bot in a new event_loop until completed.
        """
        loop: asyncio.AbstractEventLoop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)

        members_intent: bool = self.discord_replace_relayed_mentions or self.discord_replace_triggered_mentions
        intents: discord.Intents = \
            discord.Intents(members=members_intent, guilds=True, bans=False, emojis=False, integrations=False,
                            webhooks=False, invites=False, voice_states=False, presences=True, messages=True,
                            guild_messages=True, dm_messages=True, reactions=False, guild_reactions=False,
                            dm_reactions=False, typing=False, guild_typing=False, dm_typing=False, message_content=True,
                            guild_scheduled_events=True)

        # init the bot, and init the main discord interactions
        if self.discord_help_enabled:
            self.discord = Bot(command_prefix=self.discord_command_prefix,
                               application_id=self.discord_application_id,
                               description=f"{self.version_information}",
                               help_command=MinqlxHelpCommand(),
                               loop=loop,
                               intents=intents)
        else:
            self.discord = Bot(command_prefix=self.discord_command_prefix,
                               application_id=self.discord_application_id,
                               description=f"{self.version_information}",
                               help_command=None,
                               loop=loop,
                               intents=intents)

        self.initialize_bot(self.discord)

        # connect the now configured bot to discord in the event_loop
        loop.run_until_complete(self.discord.start(self.discord_bot_token))

    def initialize_bot(self, discord_bot: discord.ext.commands.Bot) -> None:
        """
        initializes a discord bot with commands and listeners on this pseudo cog class

        :param: discord_bot: the discord_bot to initialize
        """
        discord_bot.add_listener(self.on_ready)
        discord_bot.add_listener(self.on_message)

        if self.discord_version_enabled:
            discord_bot.add_command(
                Command(self.version,
                        name="version",
                        pass_context=True,
                        ignore_extra=False,
                        help="display the plugin's version information"))

    async def version(self, ctx: Context, *_args, **_kwargs) -> None:
        """
        Triggers the plugin's version information sent to discord

        :param: ctx: the context the trigger happened in
        """
        await ctx.send(f"```{self.version_information}```")

    def _format_message_to_quake(self, channel: discord.TextChannel,
                                 author: discord.Member, content: str) -> str:
        """
        Format the channel, author, and content of a message so that it will be displayed nicely in the Quake Live
        console.

        :param: channel: the channel, the message came from.
        :param: author: the author of the original message.
        :param: content: the message itself, ideally taken from message.clean_content to avoid ids of mentioned users
        and channels on the discord server.
        :return: the formatted message that may be sent back to Quake Live.
        """
        sender = author.name
        if author.nick is not None:
            sender = author.nick

        if not self.discord_show_relay_channel_names and channel.id in self.discord_relay_channel_ids:
            return f"{self.discord_message_prefix} ^6{sender}^7:^2 {content}"
        return f"{self.discord_message_prefix} ^5#{channel.name} ^6{sender}^7:^2 {content}"

    async def on_ready(self) -> None:
        """
        Function called once the bot connected. Mainly displays status update from the bot in the game console
        and server logfile, and sets the bot to playing Quake Live on discord.
        """
        extensions = Plugin.get_cvar("qlx_discord_extensions", list)
        ready_actions = []
        for extension in extensions:
            if len(extension.strip()) > 0:
                ready_actions.append(
                    self.discord.load_extension(
                        f".{extension}",
                        package="minqlx-plugins.discord_extensions"))

        self.logger.info(
            f"Logged in to discord as: {self.discord.user.name} ({self.discord.user.id})"
        )
        Plugin.msg("Connected to discord")

        ready_actions.append(
            self.discord.change_presence(activity=discord.Game(
                name="Quake Live")))
        await asyncio.gather(*ready_actions)
        await self.discord.tree.sync()
        self.logger.info("Application command tree synced!")

    async def on_message(self, message) -> None:
        """
        Function called once a message is sent through discord. Here the main interaction points either back to
        Quake Live or discord happen.
        :param: message: the message that was sent.
        """
        # guard clause to avoid None messages from processing.
        if not message:
            return

        # if the bot sent the message himself, do nothing.
        if message.author == self.discord.user:
            return

        # relay all messages from the relay channels back to Quake Live.
        if message.channel.id in self.discord_relay_channel_ids:
            content: str = message.clean_content
            if len(content) > 0:
                minqlx.CHAT_CHANNEL.reply(
                    self._format_message_to_quake(message.channel,
                                                  message.author, content))

    async def on_command_error(self, exception: Exception,
                               ctx: Context) -> None:
        """
        overrides the default command error handler so that no exception is produced for command errors

        Might be changed in the future to log those problems to the ´´`minqlx.logger```
        """

    def is_discord_logged_in(self) -> bool:
        if self.discord is None:
            return False

        return not self.discord.is_closed() and self.discord.is_ready()

    def stop(self) -> None:
        """
        stops the discord client
        """
        if self.discord is None:
            return

        asyncio.run_coroutine_threadsafe(
            self.discord.change_presence(status=discord.Status.offline),
            loop=self.discord.loop)
        asyncio.run_coroutine_threadsafe(self.discord.close(),
                                         loop=self.discord.loop)

    def relay_message(self, msg: str) -> None:
        """
        relay a message to the configured relay_channels

        :param: msg: the message to send to the relay channel
        """
        self.send_to_discord_channels(self.discord_relay_channel_ids, msg)

    def send_to_discord_channels(self, channel_ids: set[Union[str, int]],
                                 content: str) -> None:
        """
        Send a message to a set of channel_ids on discord provided.

        :param: channel_ids: the ids of the channels the message should be sent to.
        :param: content: the content of the message to send to the discord channels
        """
        if not self.is_discord_logged_in():
            return
        # if we were not provided any channel_ids, do nothing.
        if not channel_ids or len(channel_ids) == 0:
            return

        # send the message in its own thread to avoid blocking of the server
        for channel_id in channel_ids:
            channel = self.discord.get_channel(channel_id)

            if channel is None:
                continue

            asyncio.run_coroutine_threadsafe(channel.send(
                content,
                allowed_mentions=AllowedMentions(everyone=False,
                                                 users=True,
                                                 roles=True)),
                                             loop=self.discord.loop)

    def relay_chat_message(self, player: minqlx.Player, channel: str,
                           message: str) -> None:
        """
        relay a message to the given channel

        :param: player: the player that originally sent the message
        :param: channel: the channel the original message came through
        :param: message: the content of the message
        """
        if self.discord_replace_relayed_mentions:
            message = self.replace_user_mentions(message, player)
            message = self.replace_channel_mentions(message, player)

        content = f"**{discord.utils.escape_markdown(player.clean_name)}**{channel}: " \
                  f"{discord.utils.escape_markdown(message)}"

        self.relay_message(content)

    def relay_team_chat_message(self, player: minqlx.Player, channel: str,
                                message: str) -> None:
        """
        relay a team_chat message, that might be hidden to the given channel

        :param: player: the player that originally sent the message
        :param: channel: the channel the original message came through
        :param: message: the content of the message
        """
        if self.discord_replace_relayed_mentions:
            message = self.replace_user_mentions(message, player)
            message = self.replace_channel_mentions(message, player)

        content = f"**{discord.utils.escape_markdown(player.clean_name)}**{channel}: " \
                  f"{discord.utils.escape_markdown(message)}"

        self.send_to_discord_channels(self.discord_relay_team_chat_channel_ids,
                                      content)

    def replace_user_mentions(self,
                              message: str,
                              player: minqlx.Player = None) -> str:
        """
        replaces a mentioned discord user (indicated by @user-hint with a real mention)

        :param: message: the message to replace the user mentions in
        :param: player: (default: None) when several alternatives are found for the mentions used, this player is told
        what the alternatives are. No replacements for the ambiguous substitutions will happen.

        :return: the original message replaced by properly formatted user mentions
        """
        if not self.is_discord_logged_in():
            return message

        returned_message = message
        # this regular expression will make sure that the "@user" has at least three characters, and is either
        # prefixed by a space or at the beginning of the string
        matcher = re.compile("(?:^| )@([^ ]{3,})")

        member_list = list(self.discord.get_all_members())
        matches: list[re.Match] = matcher.findall(returned_message)

        for match in sorted(matches,
                            key=lambda _match: len(str(_match)),
                            reverse=True):
            if match in ["all", "everyone", "here"]:
                continue
            member = SimpleAsyncDiscord.find_user_that_matches(
                str(match), member_list, player)
            if member is not None:
                returned_message = returned_message.replace(
                    f"@{match}", member.mention)

        return returned_message

    @staticmethod
    def find_user_that_matches(match: str, member_list: list[discord.Member], player: minqlx.Player = None) \
            -> Optional[discord.Member]:
        """
        find a user that matches the given match

        :param: match: the match to look for in the username and nick
        :param: member_list: the list of members connected to the discord server
        :param: player: (default: None) when several alternatives are found for the mentions used, this player is told
        what the alternatives are. None is returned in that case.

        :return: the matching member, or None if none or more than one are found
        """
        # try a direct match for the whole name first
        member = [
            user for user in member_list if user.name.lower() == match.lower()
        ]
        if len(member) == 1:
            return member[0]

        # then try a direct match at the user's nickname
        member = [
            user for user in member_list
            if user.nick is not None and user.nick.lower() == match.lower()
        ]
        if len(member) == 1:
            return member[0]

        # if direct searches for the match fail, we try to match portions of the name or portions of the nick, if set
        member = [
            user for user in member_list
            if user.name.lower().find(match.lower()) != -1 or (
                user.nick is not None
                and user.nick.lower().find(match.lower()) != -1)
        ]
        if len(member) == 1:
            return list(member)[0]

        # we found more than one matching member, let's tell the player about this.
        if len(member) > 1 and player is not None:
            player.tell(
                f"Found ^6{len(member)}^7 matching discord users for @{match}:"
            )
            alternatives = ""
            for alternative_member in member:
                alternatives += f"@{alternative_member.name} "
            player.tell(alternatives)

        return None

    def replace_channel_mentions(self,
                                 message: str,
                                 player: minqlx.Player = None) -> str:
        """
        replaces a mentioned discord channel (indicated by #channel-hint with a real mention)

        :param: message: the message to replace the channel mentions in
        :param: player: (default: None) when several alternatives are found for the mentions used, this player is told
        what the alternatives are. No replacements for the ambiguous substitutions will happen.

        :return: the original message replaced by properly formatted channel mentions
        """
        if not self.is_discord_logged_in():
            return message

        returned_message = message
        # this regular expression will make sure that the "#channel" has at least three characters, and is either
        # prefixed by a space or at the beginning of the string
        matcher = re.compile("(?:^| )#([^ ]{3,})")

        channel_list = [
            ch for ch in self.discord.get_all_channels() if ch.type in
            [ChannelType.text, ChannelType.voice, ChannelType.group]
        ]
        matches: list[re.Match] = matcher.findall(returned_message)

        for match in sorted(matches,
                            key=lambda _match: len(str(_match)),
                            reverse=True):
            channel = SimpleAsyncDiscord.find_channel_that_matches(
                str(match), channel_list, player)
            if channel is not None:
                returned_message = returned_message.replace(
                    f"#{match}", channel.mention)

        return returned_message

    @staticmethod
    def find_channel_that_matches(
            match: str,
            channel_list: list[discord.TextChannel],
            player: minqlx.Player = None) -> Optional[discord.TextChannel]:
        """
        find a channel that matches the given match

        :param: match: the match to look for in the channel name
        :param: channel_list: the list of channels connected to the discord server
        :param: player: (default: None) when several alternatives are found for the mentions used, this player is told
        what the alternatives are. None is returned in that case.

        :return: the matching channel, or None if none or more than one are found
        """
        # try a direct channel name match case-sensitive first
        channel = [ch for ch in channel_list if ch.name == match]
        if len(channel) == 1:
            return channel[0]

        # then try a case-insensitive direct match with the channel name
        channel = [
            ch for ch in channel_list if ch.name.lower() == match.lower()
        ]
        if len(channel) == 1:
            return channel[0]

        # then we try a match with portions of the channel name
        channel = [
            ch for ch in channel_list
            if ch.name.lower().find(match.lower()) != -1
        ]
        if len(channel) == 1:
            return channel[0]

        # we found more than one matching channel, let's tell the player about this.
        if len(channel) > 1 and player is not None:
            player.tell(
                f"Found ^6{len(channel)}^7 matching discord channels for #{match}:"
            )
            alternatives = ""
            for alternative_channel in channel:
                alternatives += f"#{alternative_channel.name} "
            player.tell(alternatives)

        return None

    def triggered_message(self, player: minqlx.Player, message: str) -> None:
        """
        send a triggered message to the configured triggered_channel

        :param: player: the player that originally sent the message
        :param: message: the content of the message
        """
        if not self.discord_triggered_channel_ids:
            return

        if self.discord_replace_triggered_mentions:
            message = self.replace_user_mentions(message, player)
            message = self.replace_channel_mentions(message, player)

        if self.discord_triggered_channel_message_prefix is not None and \
                self.discord_triggered_channel_message_prefix != "":
            content = f"{self.discord_triggered_channel_message_prefix} " \
                      f"**{discord.utils.escape_markdown(player.clean_name)}**: " \
                      f"{discord.utils.escape_markdown(message)}"
        else:
            content = f"**{discord.utils.escape_markdown(player.clean_name)}**: " \
                      f"{discord.utils.escape_markdown(message)}"

        self.send_to_discord_channels(self.discord_triggered_channel_ids,
                                      content)
Ejemplo n.º 4
0
		await bop.send_message(msg.channel, 'i cant process that ^')


@bop.event
async def on_ready():
	await bop.change_presence(game=Game(name="{}... !halp".format(gameStat)))
	print("name: " + bop.user.name)

async def list_servers():
	await bop.wait_until_ready()
	print ('-'*25)
	print("Current servers connected:")
	for server in bop.servers:
		print(server.name, 'channels with access', len(server.channels))
	print ('-'*25)

	return False

try:
	bop.loop.create_task(list_servers())
	bop.run(TOKEN)
	bop.close()

except Exception as e:
	bop.close()
	print (e)

print ('(σ´-ω-`)σ')

# https://discordapp.com/api/oauth2/authorize?client_id=592786784065159188&permissions=37215296&scope=bot
Ejemplo n.º 5
0
bot.http_session = ClientSession(
    connector=TCPConnector(
        resolver=AsyncResolver(),
        family=socket.AF_INET,
    )
)

# Cog extension loaders
bot.load_extension("bot.cogs.antimalware")
bot.load_extension("bot.cogs.antispam")
bot.load_extension("bot.cogs.clean")
bot.load_extension("bot.cogs.code_evaluator")
bot.load_extension("bot.cogs.events")
bot.load_extension("bot.cogs.extensions")
bot.load_extension("bot.cogs.filtering")
bot.load_extension("bot.cogs.help")
bot.load_extension("bot.cogs.information")
bot.load_extension("bot.cogs.modlog")
bot.load_extension("bot.cogs.security")
bot.load_extension("bot.cogs.token_remover")
bot.load_extension("bot.cogs.utils")
bot.load_extension("bot.cogs.verification")
bot.load_extension("bot.cogs.wolfram")

bot.run(BotConfig.token)

loop = asyncio.get_event_loop()
loop.run_until_complete(bot.close())

bot.http_session.close()
Ejemplo n.º 6
0
import sys
import discord
from discord.channel import Channel
from discord.ext.commands import Bot

if len(sys.argv) != 3:
    print("Need to supply more arguments.")
    sys.exit(-1)
channel_id = sys.argv[1]
channel = discord.Object(id=channel_id)

client = Bot(description="haldibot.", command_prefix="-", pm_help=False)
message = sys.argv[2]
print(sys.argv)


@client.event
async def on_ready():
    channel = discord.Object(id=channel_id)
    return await client.send_message(channel, message)


if __name__ == "__main__":

    with open('discord.txt') as infile:
        token = infile.read()

    client.run(token)
    client.send_message(channel, "this is a test!")
    client.close()
Ejemplo n.º 7
0
            print("[listing_watcher]: " + str(posting_channel))

            try:
                new_listings = session.query(Listing).filter(and_(Listing.new == 1, Listing.searchurlid.in_(single_search.search_indecies))).limit(bot_config.posting_limit)

                for new_listing in new_listings:
                    await posting_channel.send(embed=new_listing.to_embed(single_search.thumbnail))
                    # Flag the listing as old
                    new_listing.new = 0

                session.commit()
            except NoResultFound:
                await posting_channel.send(content="No listings available")

            # Update the last search value in config. Used in status command
            bot_config.last_searched = datetime.now()

            # Breather between search configs
            await asyncio.sleep(2)

        # task runs every 60 seconds
        await asyncio.sleep(60)

# Run the bot with the supplied token
print('Discord.py version:', discord.__version__)

# Start the database monitoring task
bot.bg_task = bot.loop.create_task(listing_watcher())
bot.run(bot_config.token)
bot.close()
Ejemplo n.º 8
0
    wakeup = client.loop.create_task(update_if_changed())
    set_playing_status.start()
    if cmdargs.loop:
        others = subprocess.check_output(
            "ps aux | grep -v grep | grep 'kenny2automate' | awk '{print $2}'",
            shell=True).splitlines()
        for i in others:
            if int(i) == os.getpid():
                continue
            os.system('kill -2 {}'.format(i.decode('ascii')))
    server = Handler(client, db, logger, cmdargs.prefix, client_id,
                     client_secret, web_root)
    client.loop.run_until_complete(server.run())
    client.loop.run_until_complete(client.start(token))
except KeyboardInterrupt:
    pass
finally:
    sys.stdout = sys.__stdout__
    sys.stderr = sys.__stderr__
    try:
        wakeup.cancel()
        set_playing_status.cancel()
    except RuntimeError as exc:
        print(exc)  #probably already closed?
    client.loop.run_until_complete(client.close())
    client.loop.run_until_complete(server.stop())
    client.loop.stop()
    client.loop.close()
    dbw.commit()
    dbw.close()