Пример #1
0
async def rebuild_weight_table(guild: discord.Guild):
    '''
    Refills the daily member counts according to staleness rules.
    '''
    # Attempt to set default daily count
    server_cfg: dict = collection("servers").find_one(guild.id)

    if server_cfg["default-daily-count"] == 0:
        # Daily member turned off
        for member in guild.members:
            collection("users").update_one(ufil(member),
                                           {"$set": {
                                               "daily-weight": 0
                                           }})
        return

    logger.warning(
        f"{guild.name}: Cache is being reconstructed. This will take a while.")

    # Process staleness
    staleness = server_cfg["daily-member-staleness"]

    if staleness >= 0:
        threshold = datetime.datetime.now() - \
            datetime.timedelta(staleness)

        active_author_ids = set()

        for channel in guild.text_channels:

            active_ids_in_channel = await channel.history(
                limit=None, after=threshold).filter(
                    lambda msg: msg.type == discord.MessageType.default).map(
                        lambda msg: msg.author.id).flatten()

            active_author_ids.update(active_ids_in_channel)
    else:
        active_author_ids = set([member.id for member in guild.members])

    for member in guild.members:
        if (not member.bot or not bool(
            cfg["Settings"]["exclude-bots-from-daily"]))\
                and member.id in active_author_ids:
            daily = server_cfg["default-daily-count"]
        else:
            daily = 0

        collection("users").update_one(ufil(member),
                                       {"$set": {
                                           "daily-weight": daily
                                       }})

    logger.info(
        f"{guild.name}: Rebuilt weights for users in past {staleness} days")
Пример #2
0
def text_filter(content: str,
                author: discord.Member,
                guild: discord.Guild) -> bool:
    '''
    Detects banned words in a string
    '''
    spaced_query = unidecode.unidecode(emojiToText(content.lower()))
    # remove non-ascii, attempt to decode unicode,
    # get into formattable form
    query = re.sub(r"\W+", '', spaced_query)  # spaces

    server_words: dict = collection(
        "servers").find_one(guild.id)["global-banned-words"]
    words: dict = collection(
        "users").find_one(ufil(author))["moderation"]["banned-words"]

    words.update(server_words)

    # TODO with python 3.9 -- dictionary union
    for w in words:

        if words[w] < 100 and (
            len(query) > 2 and fuzz.partial_ratio(query, w) >= words[w]) or query == w:
            return True

        for word in spaced_query.split(" "):
            if word[0] == w[0] and word[-1] == w[-1] and fuzz.partial_ratio(word, w) >= words[w]:
                return True
            # checking endcap letters
    return False
Пример #3
0
 async def deop(self, ctx, *, member: discord.Member):
     '''
     de-OPs a member
     '''
     collection("users").update_one(ufil(member), {"$set": {"OP": False}})
     op_list.cache_clear()
     await ctx.send(f"{member.display_name} is no longer OP.")
Пример #4
0
    async def kick(self, ctx: commands.Context, *, member: discord.Member):
        '''
        Votes to kick a user from the server.
        '''
        user_info = collection("users").find_one(ufil(member))
        vk = user_info["moderation"]["kick-votes"]

        kickreq = collection("servers").find_one(
            ctx.guild.id)["thresholds"]["kick"]
        '''
        Dynamic kick requirement
        '''
        if type(kickreq) == str and "%" in kickreq:
            kickreq = dynamic_threshold(ctx.guild,
                                        float(kickreq.strip("%").strip()))
            await ctx.send(
                f"Dynamic mute threshold equals to {kickreq} online members needed to mute."
            )

        if ctx.author.id not in vk:
            collection("users").update_one(
                ufil(member),
                {"$push": {
                    "moderation.kick-votes": ctx.author.id
                }})
            await ctx.send(
                f"Vote to kick {member.display_name} added. ({len(vk) + 1}/{kickreq} votes)"
            )

            if len(vk) + 1 >= kickreq:

                collection("users").update_one(
                    ufil(member), {"$set": {
                        "moderation.kick-votes": []
                    }})
                await ctx.guild.kick(member)
                await ctx.send(f"{member.display_name} was kicked.")

        else:
            collection("users").update_one(
                ufil(member),
                {"$pull": {
                    "moderation.kick-votes": ctx.author.id
                }})
            await ctx.send(
                f"Vote to kick {member.display_name} removed. ({len(vk) - 1}/{kickreq} votes)"
            )
Пример #5
0
def content_filter(message: discord.Message) -> bool:
    '''
    Checks a message for additional offending characteristics based on member
    Filters pings and images
    '''
    u = collection("users").find_one(ufil(message.author))
    return (u["moderation"]["stop-pings"] and message.mentions) or (
        u["moderation"]["stop-images"] and (
            message.attachments or message.embeds))
Пример #6
0
    async def stats(self, ctx: commands.Context):
        '''
        Stats for guess
        '''
        highscore = collection("users").find_one(
            ufil(ctx.author))["guessing-game"]["highest-streak"]
        currscore = self.streak[ctx.author.id]

        await ctx.send(f"**Info for {ctx.author.display_name}**\nCurrent Streak: {currscore}\nHighest Streak: {highscore}")
Пример #7
0
    async def track(self, ctx: commands.Context, *, member: discord.Member):
        '''
        Makes it so that when a user changes status, you are notified.
        This command is a toggle.
        '''
        notifiees = collection(
            "users").find_one(ufil(member))["notify-status"]

        if ctx.author.id in notifiees:
            collection("users").update_one(
                ufil(member),
                {"$pull": {"notify-status": ctx.author.id}})
            await ctx.send(
                f"You will no longer be notified by when {member.display_name} changes their status.")
        else:
            collection("users").update_one(
                ufil(member),
                {"$push": {"notify-status": ctx.author.id}})
            await ctx.send(
                f"You will now be notified by when {member.display_name} changes their status.")
Пример #8
0
    async def on_member_update(self,
                               before: discord.Member, after: discord.Member):
        '''
        Whenever a server member changes their state.
        '''
        if after.status != before.status and after.guild.id != RELAY_ID:
            # status update

            if str(after.status) == "offline":

                collection("users").update_one(
                    ufil(after),
                    {"$set": {
                        "last-online":
                        local_time().strftime("%I:%M:%S %p %Z")}})
            else:
                collection("users").update_one(
                    ufil(after),
                    {"$set": {
                        "last-online": "Now"}})

            if col := collection(
                    "users").find_one(
                        ufil(after)):

                for mem_id in col["notify-status"]:
                    mem = after.guild.get_member(mem_id)

                    embed = discord.Embed(color=0xd7342a,
                        description=
                        f"{str(before.status)} -> {str(after.status)} @ {local_time().strftime('%I:%M:%S %p %Z')}")

                    embed.set_author(
                        name=
                        f"{after.display_name} ({str(after)}) is now {str(after.status)}.",
                        icon_url=after.avatar_url)
                    embed.set_footer(
                        text=
                        f"To unsubscribe, type [{bot_prefix}track {after.display_name}] in {after.guild.name}")

                    await mem.send(embed=embed)
Пример #9
0
    async def threat(self, ctx, level: int, *, member: discord.Member):
        '''
        sets a member's threat level
        '''
        collection("users").update_one(
            ufil(member), {"$set": {
                "moderation.threat-level": level
            }})
        threat_list.cache_clear()
        assert member.id in threat_list(ctx.guild.id, level)

        await ctx.send(f"{member.display_name} is now threat level `{level}`")
Пример #10
0
    async def dailyannounce(self, channel: discord.TextChannel):
        '''
        Daily announcement
        '''
        m = await channel.send(
            "Good morning everyone! Today is "
            f"{local_time().strftime('%A, %B %d (Day %j in %Y). Time is %I:%M %p %Z')}. Have a great day."
        )
        '''
        Daily user
        '''
        ctx = await self.bot.get_context(m)

        if luckyperson := channel.guild.get_member(
                await weighted_member_from_server(channel.guild)):

            collection("users").update_one(ufil(luckyperson),
                                           {"$inc": {
                                               "daily-weight": -1
                                           }})

            dailyrole = await role(channel.guild, "Member of the Day")

            for m in dailyrole.members:
                # remove bearer of previous daily role
                roles = m.roles
                roles.remove(dailyrole)
                await m.edit(roles=roles)
            roles = luckyperson.roles
            roles.append(dailyrole)
            await luckyperson.edit(roles=roles)

            await channel.send(
                f"Today's Daily Member is **{luckyperson.display_name}**")

            col = luckyperson.colour

            a = discord.Embed(
                color=col,
                title=f"{luckyperson.display_name}'s Avatar",
                url=str(luckyperson.avatar_url_as(static_format="png")))
            a.set_image(url=luckyperson.avatar_url)
            await ctx.send(embed=a)

            general_cog = self.bot.get_cog("General")
            await general_cog.avatar(ctx, member=luckyperson)

            if not sum_of_weights(channel.guild):
                # rebuild if necessary
                await rebuild_weight_table(channel.guild)
Пример #11
0
 async def identity(self, ctx: commands.Context,
                    member: typing.Optional[discord.Member] = None, *,
                    name: typing.Optional[str] = None):
     '''
     Looks up user with real identity, or sets their identity.
     '''
     if name and member:
         # Case: assign name to member
         collection(
             "users").update_one(
                 ufil(member),
                 {"$set": {"identity": name}})
         await reactOK(ctx)
     elif name and (real_user := collection(
         "users").find_one(
             {"identity": re.compile('^' + name + '$', re.IGNORECASE)})):
         # Case: find unknown user
         user = ctx.guild.get_member(real_user["user"])
         await self.userinfo(ctx, member=user)
Пример #12
0
    async def modchance(self, ctx,
                        member: typing.Optional[discord.Member] = None):
        '''
        Shows the chance of a user to be member of the day [MoD]
        '''
        if not member:
            member = ctx.author

        _, weights = weight_table(ctx.guild)

        sum_of_weights = sum(weights)

        count = collection(
            "users").find_one(ufil(member))["daily-weight"]

        if sum_of_weights > 0:
            await ctx.send(
                f"{member.display_name}'s chance of being rolled tomorrow is {count}/{sum_of_weights} ({round(count/sum_of_weights * 100, 2)}%)")
        else:
            await ctx.send("Daily weight cache is currently unavailable (may be being rebuilt).")
Пример #13
0
    async def requiem(self, ctx: commands.Context,
                      day: int = 30, trim=False):
        '''
        Generates a list of users who have not talked in the past x days
        '''
        threshold = datetime.datetime.now() - datetime.timedelta(day)

        active_author_ids: set = set()

        channel: discord.TextChannel
        for channel in ctx.guild.text_channels:

            active_ids_in_channel = await channel.history(
                limit=None, after=threshold).filter(
                    lambda msg: msg.type == discord.MessageType.default
            ).map(lambda msg: msg.author.id).flatten()

            active_author_ids.update(active_ids_in_channel)

        inactive_author_ids = set(
            [member.id for member in ctx.guild.members]) - active_author_ids

        await ctx.send(
            f"{len(inactive_author_ids)} members detected to have not posted in the past {day} days.")

        OP = isOP()(ctx)  # check before trimming

        s = "```"
        for member in [ctx.guild.get_member(
                    idd) for idd in inactive_author_ids]:
            s += member.display_name + "\n"

            if trim and OP:
                collection("users").update_one(
                    ufil(member), {"$set": {"daily-weight": 0}})
        s += "```"
        await ctx.send(s)

        if trim:
            await ctx.send("Users above have been removed from the daily member pool.")
Пример #14
0
    async def userinfo(self, ctx: commands.Context, *,
                       member: typing.Union[discord.Member,
                                            discord.User] = None):
        '''
        Displays User Information of said person
        Developed by Slyflare
        '''
        if not member:
            member = ctx.author

        e = discord.Embed(colour=member.colour)
        e.set_author(name=f"{member.display_name} ({member})",
                     icon_url=member.avatar_url)
        e.set_thumbnail(url=member.avatar_url)
        e.set_footer(text=f"ID: {member.id}")

        e.add_field(name="Is Human",
                    value=not member.bot)

        e.add_field(name="Account Created",
                    value=utc_to_local_time(member.created_at).strftime(
                        '%B %d %Y at %I:%M:%S %p %Z'), inline=False)

        if ctx.guild:
            user_information = collection(
                "users").find_one(ufil(member))

            e.add_field(name="Joined Server",
                        value=utc_to_local_time(member.joined_at).strftime(
                            '%B %d %Y at %I:%M:%S %p %Z'), inline=False)

            if w := user_information["daily-weight"]:
                e.add_field(name="Daily Weight", value=w)

            if w := user_information["OP"]:
                e.add_field(name="OP", value=w)
Пример #15
0
    async def guess(self, ctx: commands.Context, guess: typing.Optional[discord.Member]):
        '''
        Guessing game
        By Phtephen99 with help from Itchono, with the power of friendship and other ppl's funtions
        we created a minigame which uses the n-gram model built by Itchono where users guess who the generated
        text was based off of.
        '''

        # TODO
        '''
        - Generate a random user (done)
        - Generate some text (done)
        - Expand game (in progress)
            - Streaks(in progress)
                - Make the framework(done)
                - Integrate streaks into mongoDb database as a field for the user(in progress)
            - Powerups(in progress)
            - Better UX and UI(future plans)
            - Leaderboards(future plans)
        - Optimize code(In progress)
        '''

        if ctx.invoked_subcommand is None:
            if guess and self.guessState:
                streakPrompts = {
                    3: "guessing spree!",
                    4: "rampage!",
                    5: "unstoppable!",
                    6: "godlike!",
                    7: "legendary!",
                    8: "umm Insane?",
                    9: "... how?",
                    10: "this is getting kinda creepy ngl.",
                    11: "reaching the current max for normal streak prompts, continue to accumulate your streak to unlock bonus prompts!",
                    69: "has just won at life!",
                    420: "suuuuuuuhhhhhh *puffs out giant cloud of smoke.",
                    9999: "\nIf someone reaches this, good job, you have earned my respect - Stephen Luu June 13, 2020."}

                if guess.display_name == self.activeGuess:
                    out = "Congratulations you gave guessed right!"
                    self.guessState = False

                    try:
                        self.streak[ctx.author.id] += 1
                        if self.streak[ctx.author.id] >= 3:
                            if self.streak[ctx.author.id] <= 4:
                                out += f"\n**{ctx.author.display_name} is on a {streakPrompts[self.streak[ctx.author.id]]} Streak: {self.streak[ctx.author.id]}**"
                            elif self.streak[ctx.author.id] in streakPrompts:
                                out += f"\n**{ctx.author.display_name} is {streakPrompts[self.streak[ctx.author.id]]} Streak: {self.streak[ctx.author.id]}**"
                    except BaseException:
                        self.streak[ctx.author.id] = 1

                    await ctx.send(out)

                    udict = collection("users").find_one(
                        ufil(ctx.author))

                    if self.streak[ctx.author.id] > udict["guessing-game"]["highest-streak"]:
                        collection("users").update_one(ufil(ctx.author),
                                                       {"$set": {"guessing-game.highest-streak": self.streak[ctx.author.id]}})

                        await ctx.send(f"{ctx.author.mention} has achieved a new high score: {self.streak[ctx.author.id]}")

                else:
                    self.guessState = False
                    out = f"\nYikes that was incorrect, it was {self.activeGuess}."

                    try:
                        if self.streak[ctx.author.id] >= 3:
                            out += f"\n**OOOOOF {ctx.author.display_name}'s streak got reset back to 0 from {self.streak[ctx.author.id]}**"
                    except BaseException:
                        pass
                    self.streak[ctx.author.id] = 0
                    await ctx.send(out)

            elif not self.guessState and guess:
                await ctx.send(f"No prompt, try entering `{bot_prefix}guess` to generate a prompt")

            elif self.guessState and not guess:
                await ctx.send("There's already a prompt, try guessing for that one before asking for another prompt.")

            elif not self.guessState and not guess:
                await ctx.trigger_typing()

                iterations = 0

                txt = None
                luckyperson = None

                while not txt and iterations < 100:
                    iterations += 1
                    luckyperson: discord.Member = random_member_from_server(
                        ctx.guild.id, require_human=True)
                    # print(luckyperson.display_name.encode("UTF-8"))
                    # #Debugging print, TODO get rid when fully deployed

                    m = await ctx.send(f"Building model (This may take a while)")
                    await ctx.trigger_typing()

                    model = await text_model(ctx.channel.id, luckyperson.id)
                    await m.delete()

                    txt = generate_text(model)

                self.activeGuess = luckyperson.display_name
                await ctx.send(f'Who could have typed this? Submit your guess using `{bot_prefix}guess <your guess>`\n```{txt}```')
                self.guessState = True
Пример #16
0
    async def banword(self,
                      ctx: commands.Context,
                      member: typing.Optional[discord.Member] = None,
                      threshold: typing.Optional[int] = 100,
                      *,
                      word):
        '''
        Bans a word, with an optional person to ban the word for,
        and a percentage similarity threshold required to trigger.
        If no member is specified, the word is banned for the whole server
        If no threshold is specified, the default threshold is 100
        (exact match required)
        Use \\ to escape arguments
        '''
        word = word.lstrip("\\")  # strip \

        if member:
            user_cfg = collection("users").find_one(ufil(member))

            if word in user_cfg["moderation"]["banned-words"]:
                collection("users").update_one(
                    ufil(member),
                    {"$unset": {
                        f"moderation.banned-words.{word}": ""
                    }})
                await ctx.send(
                    f"Word has been removed from {member.display_name}'s set of personal banned words."
                )

            else:
                collection("users").update_one(
                    ufil(member),
                    {"$set": {
                        f"moderation.banned-words.{word}": threshold
                    }})
                await ctx.send(
                    f"Word has been added to {member.display_name}'s set of personal banned words.\nActivation threshold: {threshold}%"
                )

        else:
            server_cfg = collection("servers").find_one(ctx.guild.id)

            if word in server_cfg["global-banned-words"]:
                collection("servers").update_one(
                    {"_id": ctx.guild.id},
                    {"$unset": {
                        f"global-banned-words.{word}": ""
                    }})
                await ctx.send(
                    f"Word has been removed from {ctx.guild.name}'s global banned words."
                )

            else:
                collection("servers").update_one(
                    {"_id": ctx.guild.id},
                    {"$set": {
                        f"global-banned-words.{word}": threshold
                    }})
                await ctx.send(
                    f"Word has been added to {member.display_name}'s set of personal banned words.\nActivation threshold: {threshold}%"
                )
Пример #17
0
            # Case: assign name to member
            collection(
                "users").update_one(
                    ufil(member),
                    {"$set": {"identity": name}})
            await reactOK(ctx)
        elif name and (real_user := collection(
            "users").find_one(
                {"identity": re.compile('^' + name + '$', re.IGNORECASE)})):
            # Case: find unknown user
            user = ctx.guild.get_member(real_user["user"])
            await self.userinfo(ctx, member=user)

        elif member:
            await ctx.send(collection(
                "users").find_one(ufil(member))["identity"])
        else:
            await ctx.send("No identity listed")

    @commands.command()
    @commands.guild_only()
    async def requiem(self, ctx: commands.Context,
                      day: int = 30, trim=False):
        '''
        Generates a list of users who have not talked in the past x days
        '''
        threshold = datetime.datetime.now() - datetime.timedelta(day)

        active_author_ids: set = set()

        channel: discord.TextChannel
Пример #18
0
    async def mute(self, ctx: commands.Context, *, member: discord.Member):
        '''
        Votes to mute a selected user.
        As OP: Mute the user
        '''
        OP = isOP()(ctx)

        mutedrole = await role(ctx.guild, "Comrade-Mute")

        user_info = collection("users").find_one(ufil(member))
        vm = user_info["moderation"]["mute-votes"]

        mutereq = collection("servers").find_one(
            ctx.guild.id)["thresholds"]["mute"]
        '''
        Dynamic mute requirement support
        '''
        if type(mutereq) == str and "%" in mutereq:
            mutereq = dynamic_threshold(ctx.guild,
                                        float(mutereq.strip("%").strip()))
            await ctx.send(
                f"Dynamic mute threshold equals to {mutereq} online members needed to mute."
            )

        decision = "unmute" if mutedrole in member.roles else "mute"

        if ctx.author.id not in vm or OP:

            if not OP:
                collection("users").update_one(
                    ufil(member),
                    {"$push": {
                        "moderation.mute-votes": ctx.author.id
                    }})

                await ctx.send(
                    f"Vote to {decision} {member.display_name} added. ({len(vm) + 1}/{mutereq} votes)"
                )

            if len(vm) + 1 >= mutereq or OP:
                if mutedrole in member.roles:
                    roles = member.roles
                    roles.remove(mutedrole)
                    await member.edit(roles=roles)
                    await ctx.send(f"{member.display_name} was unmuted.")
                else:
                    roles = member.roles
                    roles.append(mutedrole)
                    await member.edit(roles=roles)
                    await ctx.send(f"{member.display_name} was muted.")

                    # Update perms for muted role
                    for channel in ctx.guild.channels:
                        await channel.set_permissions(mutedrole,
                                                      send_messages=False,
                                                      add_reactions=False)

                collection("users").update_one(
                    ufil(member), {"$set": {
                        "moderation.mute-votes": []
                    }})
        else:
            collection("users").update_one(
                ufil(member),
                {"$pull": {
                    "moderation.mute-votes": ctx.author.id
                }})

            await ctx.send(
                f"Vote to {decision} {member.display_name} added. ({len(vm) - 1}/{mutereq} votes)"
            )