Exemple #1
0
    async def botonly(self, ctx: commands.Context,
                      channel: Optional[discord.TextChannel]):
        """Command handler for the `botonly` command.

        This command marks a channel in the database as bot-only, so every message posted by someone else than the bot
        will be deleted immediately.

        Args:
            ctx (discord.ext.commands.Context): The context from which this command is invoked.
            channel (discord.Textchannel): The channel that is to be made bot-only
        """
        target_channel = channel if channel is not None else ctx.channel
        is_channel_botonly = self._db_connector.is_botonly(target_channel.id)

        if is_channel_botonly:
            log.info("Deactivated bot-only mode for channel [#%s]",
                     target_channel)
            self._db_connector.deactivate_botonly(target_channel.id)
        else:
            log.info("Activated bot-only mode for channel [#%s]",
                     target_channel)
            self._db_connector.activate_botonly(target_channel.id)

        is_enabled_string = 'aktiviert' if not is_channel_botonly else 'deaktiviert'
        embed = _build_botonly_embed(is_enabled_string)
        await target_channel.send(embed=embed)
Exemple #2
0
    async def add_reaction_role(self, ctx: commands.Context,
                                message: discord.Message, emoji: str,
                                role: discord.Role):
        """Command Handler for the subcommand `add` of the `reactionrole` command.

        Adds a reaction to a specified message and creates a corresponding database entry for it to work as a so called
        reaction role.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            message (discord.Message): The message to which the reaction role should be added.
            emoji (str): The emoji of the reaction which will be added.
            role (discord.Role): The specific role a member should get when adding the reaction.
        """
        if message.channel.id != self.ch_role.id:
            await ctx.send(
                f":information_source: Reaction-Roles können nur für Nachrichten im Kanal "
                f"{self.ch_role.mention} erstellt werden.")
            return
        if emoji in [reaction.emoji for reaction in message.reactions]:
            await ctx.send(
                ":x: Für den angegebenen Emoji existiert bereits eine Reaction-Role."
            )
            return

        self._db_connector.add_reaction_role(message.id, emoji, role.id)
        await message.add_reaction(emoji)
        log.info("A reaction role has been added to the message with id %s.",
                 message.id)

        await ctx.send(
            ":white_check_mark: Die Reaction-Role wurde erfolgreich erstellt.")
Exemple #3
0
    async def remove_reaction_role(self, ctx: commands.Context,
                                   message: discord.Message, emoji: str):
        """Command Handler for the subcommand `remove` of the `reactionrole` command.

        Removes the reaction from a specified message and deletes the corresponding database entry of the reaction role.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            message (discord.Message): The message from which the reaction role should be removed.
            emoji (str): The emoji of the reaction.
        """
        if message.channel.id != self.ch_role.id:
            await ctx.send(
                f":information_source: Nachrichten außerhalb des Kanals {self.ch_role.mention} können keine "
                f"Reaction-Roles besitzen.")
            return
        if emoji not in [reaction.emoji for reaction in message.reactions]:
            await ctx.send(
                ":x: Für den angegebenen Emoji existiert leider keine Reaction-Role."
            )
            return

        self._db_connector.remove_reaction_role(message.id, emoji)
        await message.clear_reaction(emoji)
        log.info(
            "A reaction role has been removed from the message with id %s.",
            message.id)

        if len(message.reactions) == 1:
            self._db_connector.remove_reaction_role_uniqueness_group(
                message.id)

        await ctx.send(
            ":white_check_mark: Die Reaction-Role wurde erfolgreich entfernt.")
Exemple #4
0
    async def delete_community_room(self, member: discord.Member,
                                    before: discord.VoiceState,
                                    after: discord.VoiceState):
        """Event listener which triggers if the VoiceState of a member changes.

        Deletes a Community Room (consisting of a voice and text channel) if no members are left in the corresponding
        voice channel.

        Args:
            member (discord.Member): The member whose VoiceState changed.
            before (discord.VoiceState): The previous VoiceState.
            after (discord.VoiceState): The new VoiceState.
        """
        if before.channel and before.channel.category_id in {self.cat_gaming_rooms.id, self.cat_study_rooms.id} and \
           before.channel != after.channel and len(before.channel.members) == 0:
            reason = "No one was left in Community Room."
            txt_ch_name = re.sub(
                r"[^\w\s-]", "",
                before.channel.name.lower())  # Remove non-word chars except WS
            txt_ch_name = re.sub(r"\s", "-",
                                 txt_ch_name)  # Replace whitespaces with "-"
            txt_ch = next(ch for ch in before.channel.category.text_channels
                          if ch.name == txt_ch_name)

            await before.channel.delete(reason=reason)
            await txt_ch.delete(reason=reason)
            log.info(
                "Empty Community Room [%s] has been automatically deleted.",
                before.channel.name)
Exemple #5
0
    async def clear_reaction_roles(self, ctx: commands.Context,
                                   message: discord.Message):
        """Command Handler for the subcommand `clear` of the `reactionrole` command.

        Removes all the reaction from a specified message and deletes all the corresponding database entry of the
        reaction roles.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            message (discord.Message): The message from which the reaction roles should be removed.
        """
        if message.channel.id != self.ch_role.id:
            await ctx.send(
                f":information_source: Nachrichten außerhalb des Kanals {self.ch_role.mention} können keine "
                f"Reaction-Roles besitzen.")
            return
        had_reaction_roles = self._db_connector.clear_reaction_roles(
            message.id)
        self._db_connector.remove_reaction_role_uniqueness_group(message.id)

        if not had_reaction_roles:
            await ctx.send(
                "Die von dir angegebene Nachricht hat keine Reaction-Roles. :face_with_monocle:"
            )
            return

        await message.clear_reactions()
        log.info(
            "All reaction roles of the message with id %s have been removed.",
            message.id)

        await ctx.send(
            ":white_check_mark: Die Reaction-Roles wurden erfolgreich entfernt."
        )
Exemple #6
0
    async def loop_music(self, ctx):
        """Toggles the loop mode if the bot is currently playing songs."""

        if ctx.voice_client and ctx.voice_client.is_playing() and ctx.voice_client.channel == ctx.author.voice.channel:
            self.loop_mode = not self.loop_mode
            log.info("The loop mode of the music player has been changed by %s.", ctx.author)
            await ctx.send("Die aktuelle Wiedergabeliste läuft nun in Dauerschleife.",
                           delete_after=const.TIMEOUT_INFORMATION)
Exemple #7
0
    async def kofi_notification(self, request):
        """Request handler for receiving and processing Ko-fi notifications."""
        log.info("The Web Server has received a request.")
        data = loads((await request.post())["data"])

        if data["type"] in ["Donation", "Subscription"]:
            await _send_kofi_notification_embed(self.bot, data)
            log.info("Ko-fi Notification Embed has been posted.")
            return web.Response(status=200, reason="OK")

        return web.Response(status=400, reason="Bad Request")
Exemple #8
0
async def _remove_ersti_role():
    """Method which is being called by the scheduler and removes the 'ersti' role from older members."""
    role = UniversityCog.bot.get_guild(int(constants.SERVER_ID)).get_role(int(constants.ROLE_ID_ERSTI))
    date_now = datetime.now()
    date_threshold = datetime(date_now.year, date_now.month - 2, 1)

    for member in role.members:
        if member.joined_at < date_threshold:
            await member.remove_roles(role, reason="Member isn't an Ersti anymore.")

    log.info("Ersti Role members have been purged.")
Exemple #9
0
    async def toggle_module(self, ctx: commands.Context, *, str_modules: str):
        """Command Handler for the `module` command.

        Allows members to assign/remove so called mod roles to/from themselves. This way users can toggle text channels
        about specific courses to be visible or not to them. When the operation is finished, SAM will send an overview
        about the changes he did per direct message to the user who invoked the command.
        Keep in mind that this only works if the desired role has been whitelisted as a module role by the bot owner.

        If the command is invoked outside of the configured role channel, the bot will post a short info that this
        command should only be invoked there and delete this message shortly after.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            str_modules (str): A string containing abbreviations of all the modules a user would like to toggle.
        """
        if ctx.channel.id != self.ch_role.id:
            if not self._db_connector.is_botonly(ctx.channel.id):
                await ctx.message.delete()

            await ctx.channel.send(content=f"Dieser Befehl wird nur in {self.ch_role.mention} unterstützt. Bitte "
                                   f"versuche es dort noch einmal.", delete_after=constants.TIMEOUT_INFORMATION)
            return

        converter = commands.RoleConverter()
        modules = list(set(str_modules.split()))  # Removes duplicates

        modules_error = []
        modules_added = []
        modules_removed = []

        for module in modules:
            module_upper = module.upper()
            try:
                role = await converter.convert(ctx, module_upper)

                if not self._db_connector.check_module_role(role.id):
                    raise commands.BadArgument("The specified role hasn't been whitelisted as a module role.")

                if role in ctx.author.roles:
                    await ctx.author.remove_roles(role, atomic=True, reason="Selbstständig entfernt via SAM.")
                    modules_removed.append(module_upper)
                else:
                    await ctx.author.add_roles(role, atomic=True, reason="Selbstständig zugewiesen via SAM.")
                    modules_added.append(module_upper)
            except commands.BadArgument:
                modules_error.append(module_upper)

        if len(modules_error) < len(modules):
            log.info("Module roles of the member %s have been changed.", ctx.author)

        embed = _create_embed_module_roles(modules_added, modules_removed, modules_error)
        await ctx.author.send(embed=embed)
Exemple #10
0
    async def stop_music(self, ctx):
        """Stops and disconnects the bot from the voice channel and resets all instance variables.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
        """

        if ctx.voice_client and ctx.voice_client.channel == ctx.author.voice.channel:
            await ctx.voice_client.disconnect()
            self.song_queue.clear()
            self.loop_mode = False

            log.info("%s stopped the playback", ctx.author)
Exemple #11
0
    async def remove_module_role(self, ctx: commands.Context, module_name: str):
        """Command Handler for the `module` subcommand `remove`.

        Allows the bot owner to remove a specific role from the module roles.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            module_name (str): The name of the role which should be removed.
        """
        module_role = await commands.RoleConverter().convert(ctx, module_name.upper())

        self._db_connector.remove_module_role(module_role.id)
        log.info("Role \"%s\" has been disabled as a module role.", module_role)

        await ctx.send(f"Die Rolle \"**__{module_role}__**\" wurde aus den verfügbaren Modul-Rollen entfernt.")
Exemple #12
0
async def _scheduled_group_exchange_closing_and_purge():
    """Method which is being called by the scheduler and closes the group exchange channel.

    Makes the channel invisible by removing the read_messages permission from @everyone and purging all messages in
    the channel.
    """
    guild = UniversityCog.bot.get_guild(int(constants.SERVER_ID))
    ch_group_exchange = guild.get_channel(int(constants.CHANNEL_ID_GROUP_EXCHANGE))

    overwrite = ch_group_exchange.overwrites_for(guild.default_role)
    overwrite.update(read_messages=False)
    await ch_group_exchange.set_permissions(guild.default_role, overwrite=overwrite)

    # Limit is set to a high number because we can't simply remove "all" messages.
    await ch_group_exchange.purge(limit=100000)

    log.info("Group Exchange channel has been closed.")
Exemple #13
0
async def _scheduled_group_exchange_opening():
    """Method which is being called by the scheduler and opens the group exchange channel.

    Makes the channel visible to all members by adding adding the read_messages permission to @everyone. It also
    posts an info message on how to use this service.
    """
    guild = UniversityCog.bot.get_guild(int(constants.SERVER_ID))
    ch_group_exchange = guild.get_channel(int(constants.CHANNEL_ID_GROUP_EXCHANGE))

    overwrite = ch_group_exchange.overwrites_for(guild.default_role)
    overwrite.update(read_messages=True)
    await ch_group_exchange.set_permissions(guild.default_role, overwrite=overwrite)

    embed = _build_group_exchange_info_embed()
    message = await ch_group_exchange.send(embed=embed)
    await message.pin(reason="Tauschbörse: Info-Nachricht")

    log.info("Group Exchange channel has been opened.")
Exemple #14
0
    async def add_module_role(self, ctx: commands.Context, module_name: str):
        """Command Handler for the `module` subcommand `add`.

        Allows the bot owner to add a specific role to the module roles.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            module_name (str): The name of the role which should be added.
        """
        module_role = await commands.RoleConverter().convert(ctx, module_name.upper())

        try:
            self._db_connector.add_module_role(module_role.id)
            log.info("Role \"%s\" has been whitelisted as a module role.", module_role)

            await ctx.send(f"Die Rolle \"**__{module_role}__**\" wurde erfolgreich zu den verfügbaren Modul-Rollen "
                           f"hinzugefügt.")
        except IntegrityError:
            await ctx.send(f"Die Rolle \"**__{module_role}__**\" gehört bereits zu den verfügbaren Modul-Rollen.")
Exemple #15
0
    async def suggestion_deny(self, ctx: commands.Context, suggestion_id: int,
                              *, reason: Optional[str]):
        """Command Handler for the `suggestion` subcommand `deny`.

        Allows the owner to mark a suggestion as denied and add an optional reason to it. If this is the case, the
        corresponding embed will be adapted to represent this change and the user who submitted the idea will be
        notified via DM.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            suggestion_id (int): The id of the suggestion which should be approved.
            reason (Optional[str]): The reason for the decision.
        """
        if not self._db_connector.is_botonly(ctx.channel.id):
            await ctx.message.delete()

        await self._change_suggestion_status(suggestion_id,
                                             SuggestionStatus.DENIED,
                                             ctx.author, reason)
        log.info("Suggestion #%s has been denied by %s.", suggestion_id,
                 ctx.author)
Exemple #16
0
    async def play_music(self, ctx, *, url):
        """Starts the streaming of music provided by URLs or adds them to the song queue.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            url (str): The URL to the song or playlist.
        """
        _check_if_supported_url(url)
        log.info("%s has started playback for the URL \"%s\".", ctx.author, url)

        try:
            media_list = await YTDLSource.from_url(url, loop=self.bot.loop)
        except discord.InvalidArgument as error:
            log.error(error)
            await ctx.send("Der von dir angegebene Link ist leider ungültig.", delete_after=const.TIMEOUT_INFORMATION)
            return

        if len(self.song_queue) == const.LIMIT_SONG_QUEUE:
            self.song_queue = self.song_queue[len(media_list):]

        for song_url in media_list:
            self.song_queue.append(song_url)  # Use list.append() because it's thread safe.

        if not ctx.voice_client.is_playing():
            while True:
                for song_url in self.song_queue:
                    try:
                        source = await YTDLSource.get_media(song_url, loop=self.bot.loop)
                        await _stream_media(ctx.voice_client, self.bot.loop, source)
                    except discord.InvalidArgument as error:
                        log.error(error)
                        self.song_queue.remove(song_url)

                if not self.loop_mode:
                    break
        else:
            info_message = ("Die Songs wurden" if len(media_list) > 1 else "Der Song wurde") + \
                           " erfolgreich der Wiedergabeliste hinzugefügt."
            await ctx.send(info_message, delete_after=const.TIMEOUT_INFORMATION)
Exemple #17
0
    async def welcome_message(self, user: discord.Member):
        """Event listener which triggers if a user joins the server.

        If the server gets a new member, the bot automatically welcomes him by sending a private message with some
        usefull tips.

        Args:
            user (discord.Member): The new member on the server.
        """
        log.info("%s has joined the server!", user)

        content = "Hallo! :wave: :grinning:\nIch bin **SAM**, der Management-Bot für den Discord-Server der " \
                  "**__Uni Wien INF/WINF__**. Es freut mich sehr, dass du zu uns gefunden hast!\n\nHier ein paar " \
                  "Tipps damit du sofort durchstarten kannst:"

        description = "**- Hol dir als __allererstes__ ein paar Rollen im Channel <#{0}>.**\n" \
                      "Was das genau bedeutet und wie es funktioniert, wird dir dort in einer kurzen Anleitung " \
                      "erklärt.\n\n" \
                      "**- Lies dir unsere <#{1}> durch.**\n" \
                      "Wir legen großen Wert darauf, dass sich auch wirklich jeder auf unserem Server wohlfühlt. Um " \
                      "dies zu gewährleisten, gibt es eine Gruppe an Moderatoren, die für die Einhaltung der Regeln " \
                      "sorgen und bei Problemen auch jederzeit kontaktiert werden können.\n\n" \
                      "**- Sieh dir die <#{2}> an.**\n" \
                      "Dort findest du Antworten zu den am häufigsten gestellten Fragen. Sollte das nicht " \
                      "ausreichen, dann stelle einfach eine neue Frage in <#{3}> oder schreibe einen " \
                      "Moderator direkt an.\n\n" \
                      "**- Hab Spaß und sei Teil der Community! :heart:**\n" \
                      "Unser Ziel ist es eine zentrale Anlaufstelle für Studierende zu schaffen, was nur mithilfe " \
                      "unserer Mitglieder funktionieren kann. Stelle/Beantworte Fragen, teile Unterlagen/Lösungen " \
                      "mit anderen und starte bzw. nimm an Diskussionen teil. Getrau dich ruhig aktiv zu sein, wir " \
                      "sind hier auf Discord sowieso alle anonym. :spy:\n" \
            .format(constants.CHANNEL_ID_ROLES, constants.CHANNEL_ID_RULES, constants.CHANNEL_ID_FAQ,
                    constants.CHANNEL_ID_QUESTIONS)

        embed = discord.Embed(description=description, color=constants.EMBED_COLOR_INFO) \
            .add_field(name=constants.ZERO_WIDTH_SPACE, value="> **Ein Studium ist nicht immer leicht, aber "
                                                              "__gemeinsam__ schaffen wir das!** :muscle:")
        await user.send(content=content, embed=embed)
Exemple #18
0
    async def toggle_reaction_roles_exclusiveness(self, ctx: commands.Context, message: discord.Message):
        """Command Handler for the subcommand `unique` of the `reactionrole` command.

        Marks all the reaction roles of a message as "unique" by adding the message id to a specific table in the db.
        This means that users can only have one of the configured reaction roles of this message at a time.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            message (discord.Message): The message from which the reaction roles should be removed.
        """
        if len(message.reactions) == 0:
            await ctx.send(":x: Die angegebene Nachricht besitzt keine Reaction-Roles.")
            return

        if self._db_connector.is_reaction_role_uniqueness_group(message.id):
            self._db_connector.remove_reaction_role_uniqueness_group(message.id)
            log.info("A reaction role has been added to the message with id %s.", message.id)

            await ctx.send(":white_check_mark: Die Reaction-Roles der angegebenen Nachricht sind nicht mehr "
                           "\"exklusiv\".")
        else:
            self._db_connector.add_reaction_role_uniqueness_group(message.id)
            await ctx.send(":white_check_mark: Die Reaction-Roles der angegebenen Nachricht sind nun \"exklusiv\".")
Exemple #19
0
    async def pin_message(self, payload: discord.RawReactionActionEvent):
        """Event listener which triggers if a reaction has been added to a message.

        If enough users have reacted with the specified pin emoji to a specific message, it will be pinned in the
        corresponding channel by SAM. If the maximum of pinned messages in a channel has been reached, a message will
        be posted to inform the members.

        Args:
            payload (discord.RawReactionActionEvent): The payload for the triggered event.
        """
        if not payload.member.bot and payload.emoji.name == constants.EMOJI_PIN:
            channel = self.bot.get_guild(int(constants.SERVER_ID)).get_channel(payload.channel_id)
            message = await channel.fetch_message(payload.message_id)

            reaction = next(x for x in message.reactions if x.emoji == constants.EMOJI_PIN)

            if not message.pinned and reaction.count >= constants.LIMIT_PINS:
                try:
                    await message.pin(reason="Ausreichend Nutzer haben mit dem Pin-Emoji reagiert.")
                    log.info("A message has been pinned in channel %s via user reactions.", channel)
                except discord.HTTPException:
                    channel.send("Es sieht so aus, als wurden bereits zu viele Nachrichten in diesem Channel "
                                 "angepinnt. :pushpin:\nEin {0} könnte in diesem Fall die Pins ein wenig aufräumen. "
                                 ":broom:".format(self.role_moderator.mention))
Exemple #20
0
    async def mark_as_highlight(self, payload: discord.RawReactionActionEvent):
        """Event listener which triggers if a reaction has been added to a message.

        If enough users react to a message with the specified highlight emoji, it will be reposted in the configured
        highlights channel by SAM. This way even users who don't have access to specific channels are able to see
        interesting content from somewhere on the server.
        If recently a highlight message has already been posted for a specific message, the reaction counter inside its
        embed will be modified.

        Args:
            payload (discord.RawReactionActionEvent): The payload for the triggered event.
        """
        if payload.emoji.name != const.EMOJI_HIGHLIGHT or payload.channel_id == int(const.CHANNEL_ID_HIGHLIGHTS) \
                or self._db_connector.is_botonly(payload.channel_id):
            return

        guild = self.bot.get_guild(payload.guild_id)
        message_channel = guild.get_channel(payload.channel_id)
        message = await message_channel.fetch_message(payload.message_id)
        reaction = next(x for x in message.reactions
                        if x.emoji == const.EMOJI_HIGHLIGHT)

        has_author_reacted = await reaction.users().get(id=message.author.id)
        reaction_counter = reaction.count - 1 if has_author_reacted else reaction.count

        highlight_channel = guild.get_channel(int(const.CHANNEL_ID_HIGHLIGHTS))
        highlight_message = await _check_if_already_highlight(
            highlight_channel, message.id)

        if highlight_message:
            embed = highlight_message.embeds[0]
            embed.set_field_at(
                0,
                name=f"{const.EMOJI_HIGHLIGHT} {reaction_counter}",
                value=const.ZERO_WIDTH_SPACE)
            await highlight_message.edit(
                content=
                f"Sieht so aus als hätte sich {message.author.mention} einen Platz in "
                f"der Ruhmeshalle verdient! :tada:",
                embed=embed)
            log.info(
                "The highlight embed of the message with id \"%s\" has been updated.",
                message.id)

        elif reaction_counter == const.LIMIT_HIGHLIGHT:
            # Check if an image has been attached to the original message. If yes, take the first image and pass it to
            # the method which builds the embed so that it will be displayed inside it. Every other image or type of
            # attachment should be attached to a second message which will be send immediately after the highlight embed
            # because they can't be included in the embed.
            image = next(
                (a for a in message.attachments
                 if a.filename.split(".")[-1].lower() in
                 ["jpg", "jpeg", "png", "gif"] and not a.is_spoiler()), None)
            files = [
                await a.to_file(spoiler=a.is_spoiler())
                for a in message.attachments if a != image
            ]

            embed = _build_highlight_embed(
                message, image,
                guild.get_channel(int(const.CHANNEL_ID_ROLES)).name)
            await highlight_channel.send(
                f"Sieht so aus als hätte sich {message.author.mention} einen Platz in der "
                f"Ruhmeshalle verdient! :tada:",
                embed=embed)
            if files:
                async with highlight_channel.typing():
                    await highlight_channel.send(
                        ":paperclip: **Dazugehörige Attachments:**",
                        files=files)

            log.info(
                "A highlight embed for the message with id \"%s\" has been posted in the configured highlights "
                "channel.", message.id)
Exemple #21
0
    async def create_community_room(self, ctx: commands.Context,
                                    ch_category: discord.CategoryChannel,
                                    ch_name: Optional[str],
                                    user_limit: Optional[int]):
        """Method which creates a temporary community room requested via the study/game room commands.

        Additionally it validates the configured limits (max. amount of community rooms, valid user limit, one room
        per member) and raises an exception if needed.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            ch_name (Optional[str]): The name of the channel provided by the member.
            user_limit (int): The user limit for the voice channel provided by the member.
        """
        if len(ch_category.voice_channels) >= const.LIMIT_COMMUNITY_CHANNELS:
            raise RuntimeWarning(
                "Too many Community Rooms of this kind at the moment.")
        if user_limit and (user_limit < 1 or user_limit > 99):
            raise discord.InvalidArgument(
                "User limit cannot be outside range from 1 to 99.")
        if any(True for ch in self.cat_gaming_rooms.voice_channels if ctx.author in ch.overwrites) or \
           any(True for ch in self.cat_study_rooms.voice_channels if ctx.author in ch.overwrites):
            raise NotImplementedError(
                "Member already has an active Community Room.")

        limit: Optional[int]
        if ch_name and not user_limit:
            try:
                limit = int(ch_name)
                name = f"{ctx.author.display_name}'s Room"
            except ValueError:
                limit = None
                name = ch_name
        else:
            name = f"{ctx.author.display_name}'s Room" if ch_name is None else ch_name
            limit = user_limit

        # Remove channel number if user has added it himself.
        regex = re.search(r"(\[#\d+])", name)
        if regex:
            name = name.replace(regex.group(1), "")

        ch_number_addition = _determine_channel_number(ch_category, name)
        if ch_number_addition:
            name += ch_number_addition

        if len(name) > 100:
            name = name[:100]

        reason = f"Manuell erstellt von {ctx.author} via SAM."

        # Voice Channel
        bitrate = 96000  # 96 Kbit/s
        overwrites_voice = ch_category.overwrites
        overwrites_voice[ctx.author] = discord.PermissionOverwrite(
            priority_speaker=True,
            move_members=True,
            mute_members=True,
            deafen_members=True)
        await ch_category.create_voice_channel(name=name,
                                               user_limit=limit,
                                               bitrate=bitrate,
                                               overwrites=overwrites_voice,
                                               reason=reason)

        # Text Channel
        channel_type = "Game" if ch_category == self.cat_gaming_rooms else "Study"
        topic = f"Temporärer {channel_type}-Channel. || Erstellt von: {ctx.author.display_name}"
        await ch_category.create_text_channel(name=name,
                                              topic=topic,
                                              reason=reason)

        log.info("Temporary %s Room created by %s", channel_type, ctx.author)
        await ctx.send(
            f":white_check_mark: Der {channel_type}-Room wurde erfolgreich erstellt!",
            delete_after=const.TIMEOUT_INFORMATION)
Exemple #22
0
async def _notify_presence_change(channel: discord.TextChannel,
                                  author: discord.Member):
    log.info("The bots presence has been changed by %s", author)
    await channel.send(
        content=":white_check_mark: Die Präsenz wurde erfolgreich aktualisiert!"
    )
Exemple #23
0
    async def create_gaming_room(self, ctx: commands.Context,
                                 ch_name: Optional[str],
                                 user_limit: Optional[int]):
        """Command Handler for the `gameroom` command.

        Allows users to create temporary "Game Rooms" consisting of a voice and text channel in the configured game room
        category on the server. The member who created the room gets permissions for pinning/deleting messages in the
        text channel and for muting/deafening members in the voice channel.
        If no members are left in the voice channel, it will be deleted as well as the corresponding text channel.

        Args:
            ctx (discord.ext.commands.Context): The context in which the command was called.
            ch_name (Optional[str]): The name of the channel provided by the member.
            user_limit (int): The user limit for the voice channel provided by the member.
        """
        if not self._db_connector.is_botonly(ctx.channel.id):
            await ctx.message.delete()

        if len(self.cat_gaming_channels.channels
               ) >= const.LIMIT_GAMING_CHANNELS:
            raise RuntimeWarning("Too many game rooms at the moment.")
        if user_limit and (user_limit < 1 or user_limit > 99):
            raise discord.InvalidArgument(
                "User limit cannot be outside range from 1 to 99.")
        if any(True for ch in self.cat_gaming_channels.voice_channels
               if ctx.author in ch.overwrites):
            raise NotImplementedError(
                "Member already has an active Game Room.")

        limit: Optional[int]
        if ch_name and not user_limit:
            try:
                limit = int(ch_name)
                name = f"{ctx.author.display_name}'s Room"
            except ValueError:
                limit = None
                name = ch_name
        else:
            name = f"{ctx.author.display_name}'s Room" if ch_name is None else ch_name
            limit = user_limit

        # Remove channel number if user has added it himself.
        regex = re.search(r"(\[#\d+])", name)
        if regex:
            name = name.replace(regex.group(1), "")

        ch_number_addition = self._determine_channel_number(name)
        if ch_number_addition:
            name += ch_number_addition

        if len(name) > 100:
            name = name[:100]

        reason = f"Manuell erstellt von {ctx.author} via SAM."

        # Voice Channel
        bitrate = 96000  # 96 Kbit/s
        overwrites_voice = self.cat_gaming_channels.overwrites
        overwrites_voice[ctx.author] = discord.PermissionOverwrite(
            priority_speaker=True,
            move_members=True,
            mute_members=True,
            deafen_members=True)
        await self.cat_gaming_channels.create_voice_channel(
            name=name,
            user_limit=limit,
            bitrate=bitrate,
            overwrites=overwrites_voice,
            reason=reason)

        # Text Channel
        topic = f"Temporärer Gaming-Kanal. || Erstellt von: {ctx.author.display_name}"
        await self.cat_gaming_channels.create_text_channel(name=name,
                                                           topic=topic,
                                                           reason=reason)

        log.info("Temporary Game Room created by %s", ctx.author)
        await ctx.send(
            ":white_check_mark: Der Game-Room wurde erfolgreich erstellt!",
            delete_after=const.TIMEOUT_INFORMATION)