async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent,
                                 **kargs: Any) -> None:

    if not payload.guild_id: return

    inc_statistics_better(payload.guild_id, "on-raw-reaction-remove",
                          kargs["kernel_ramfs"])

    client = kargs["client"]
    rrconf: Optional[Dict[str, Dict[str, int]]] = load_message_config(
        payload.guild_id, kargs["ramfs"],
        datatypes=reactionrole_types)["reaction-role-data"]

    if rrconf:
        emojiname = emojifrompayload(payload)
        if str(payload.message_id) in rrconf and emojiname in rrconf[str(
                payload.message_id)]:
            role_id = rrconf[str(payload.message_id)][emojiname]
            if (guild := (await client.fetch_guild(
                    payload.guild_id
            ))) and (member := (await guild.fetch_member(
                    payload.user_id))) and (role := guild.get_role(role_id)):
                try:
                    await member.remove_roles(role)
                except discord.errors.Forbidden:
                    pass
예제 #2
0
async def on_message_delete(message: discord.Message, **kargs: Any) -> None:

    client: discord.Client = kargs["client"]
    kernel_ramfs: lexdpyk.ram_filesystem = kargs["kernel_ramfs"]
    ramfs: lexdpyk.ram_filesystem = kargs["ramfs"]

    # Ignore bots
    if parse_skip_message(client, message):
        return
    elif not message.guild:
        return

    files: Optional[List[discord.File]] = grab_files(message.guild.id, message.id, kernel_ramfs, delete=True)

    inc_statistics_better(message.guild.id, "on-message-delete", kernel_ramfs)

    # Add to log
    with db_hlapi(message.guild.id) as db:
        message_log = db.grab_config("message-log")

    try:
        if not (message_log and (log_channel := client.get_channel(int(message_log)))):
            return
    except ValueError:
        try:
            await message.channel.send("ERROR: message-log config is corrupt in database, please reset")
        except discord.errors.Forbidden:
            pass
        return

    if not isinstance(log_channel, discord.TextChannel):
        return

    message_embed = discord.Embed(
        title=f"Message deleted in #{message.channel}", description=message.content[:constants.embed.description], color=load_embed_color(message.guild, embed_colors.deletion, ramfs)
        )

    # Parse for message lengths >2048 (discord now does 4000 hhhhhh)
    if len(message.content) > constants.embed.description:
        limend = constants.embed.description + constants.embed.field.value
        message_embed.add_field(name="(Continued)", value=message.content[constants.embed.description:limend])

        if len(message.content) > limend:
            flimend = limend + constants.embed.field.value
            message_embed.add_field(name="(Continued further)", value=message.content[limend:flimend])

    message_embed.set_author(name=f"{message.author} ({message.author.id})", icon_url=user_avatar_url(message.author))

    if (r := message.reference) and (rr := r.resolved) and isinstance(rr, discord.Message):
        message_embed.add_field(name="Replying to:", value=f"{rr.author.mention} [(Link)]({rr.jump_url})")
예제 #3
0
async def on_ready(**kargs: Any) -> None:

    inc_statistics_better(0, "on-ready", kargs["kernel_ramfs"])

    Client: discord.Client = kargs["client"]
    print(f'{Client.user} has connected to Discord!')

    # Warn if user is not bot
    if not Client.user.bot:
        print(
            "WARNING: The connected account is not a bot, as it is against ToS we do not condone user botting"
        )

    # bot start time check to not reparse timers on network disconnect
    if kargs["bot_start"] > (time.time() - 10):

        with db_hlapi(None) as db:
            mutes: List[Tuple[str, str, str, int]] = db.fetch_all_mutes()
            lost_mutes = sorted(mutes, key=lambda a: a[3])

        ts = datetime_now().timestamp()

        lost_mute_timers = [i for i in lost_mutes if 0 != i[3]]

        if lost_mute_timers:

            print(f"Lost mutes: {len(lost_mute_timers)}")
            for i in lost_mute_timers:
                if i[3] < ts:
                    await attempt_unmute(Client, i)

            lost_mute_timers = [i for i in lost_mute_timers if i[3] >= ts]
            if lost_mute_timers:
                print(
                    f"Mute timers to recover: {len(lost_mute_timers)}\nThis process will end in {round(lost_mutes[-1][3]-time.time())} seconds"
                )

                for i in lost_mute_timers:
                    await asyncio.sleep(i[3] - datetime_now().timestamp())
                    with db_hlapi(int(i[0])) as db:
                        if db.is_muted(infractionid=i[1]):
                            await attempt_unmute(Client, i)

            print("Mutes recovered")
예제 #4
0
async def on_message_edit(old_message: discord.Message, message: discord.Message, **kargs: Any) -> None:

    client: discord.Client = kargs["client"]
    ramfs: lexdpyk.ram_filesystem = kargs["ramfs"]
    kernel_ramfs: lexdpyk.ram_filesystem = kargs["kernel_ramfs"]

    # Ignore bots
    if parse_skip_message(client, message):
        return
    elif not message.guild:
        return

    inc_statistics_better(message.guild.id, "on-message-edit", kernel_ramfs)

    # Add to log
    with db_hlapi(message.guild.id) as db:
        message_log_str = db.grab_config("message-edit-log") or db.grab_config("message-log")

    # Skip logging if message is the same or mlog doesnt exist
    if message_log_str and not (old_message.content == message.content):
        if message_log := client.get_channel(int(message_log_str)):

            if not isinstance(message_log, discord.TextChannel):
                return

            lim: int = constants.embed.field.value

            message_embed = discord.Embed(title=f"Message edited in #{message.channel}", color=load_embed_color(message.guild, embed_colors.edit, ramfs))
            message_embed.set_author(name=f"{message.author} ({message.author.id})", icon_url=user_avatar_url(message.author))

            old_msg = (old_message.content or "NULL")
            message_embed.add_field(name="Old Message", value=(old_msg)[:lim], inline=False)
            if len(old_msg) > lim:
                message_embed.add_field(name="(Continued)", value=(old_msg)[lim:lim * 2], inline=False)

            msg = (message.content or "NULL")
            message_embed.add_field(name="New Message", value=(msg)[:lim], inline=False)
            if len(msg) > lim:
                message_embed.add_field(name="(Continued)", value=(msg)[lim:lim * 2], inline=False)

            message_embed.set_footer(text=f"Message ID: {message.id}")
            message_embed.timestamp = datetime_now()
            asyncio.create_task(catch_logging_error(message_log, message_embed, None))
예제 #5
0
async def on_member_update(before: discord.Member, after: discord.Member, **kargs: Any) -> None:

    inc_statistics_better(before.guild.id, "on-member-update", kargs["kernel_ramfs"])

    username_log = load_message_config(before.guild.id, kargs["ramfs"], datatypes=join_leave_user_logs)["username-log"]

    if username_log and (channel := kargs["client"].get_channel(int(username_log))):
        if before.nick == after.nick:
            return

        message_embed = discord.Embed(title="Nickname updated", color=load_embed_color(before.guild, embed_colors.edit, kargs["ramfs"]))
        message_embed.set_author(name=f"{before} ({before.id})", icon_url=user_avatar_url(before))
        message_embed.add_field(name=("Before" + " | False" * (not before.nick)), value=str(before.nick))
        message_embed.add_field(name=("After" + " | False" * (not after.nick)), value=str(after.nick))

        message_embed.timestamp = ts = datetime_now()
        message_embed.set_footer(text=f"unix: {int(ts.timestamp())}")

        await catch_logging_error(channel, message_embed)
예제 #6
0
async def on_reaction_add(reaction: discord.Reaction, user: discord.User,
                          **kargs: Any) -> None:

    client: discord.Client = kargs["client"]
    kernel_ramfs: lexdpyk.ram_filesystem = kargs["kernel_ramfs"]
    ramfs: lexdpyk.ram_filesystem = kargs["ramfs"]

    message = reaction.message

    # Skip if not a guild
    if not message.guild:
        return

    inc_statistics_better(message.guild.id, "on-reaction-add", kernel_ramfs)
    mconf = load_message_config(message.guild.id,
                                ramfs,
                                datatypes=starboard_cache)

    if bool(
            int(mconf["starboard-enabled"])
    ) and reaction.emoji == mconf["starboard-emoji"] and reaction.count >= int(
            mconf["starboard-count"]):
        if (channel_id :=
                mconf["starboard-channel"]) and (channel := client.get_channel(
                    int(channel_id))) and isinstance(channel,
                                                     discord.TextChannel):

            with db_hlapi(message.guild.id) as db:
                with db.inject_enum_context("starboard",
                                            [("messageID", str)]) as starboard:
                    if not (starboard.grab(str(message.id))) and not (
                            int(channel_id) == message.channel.id):

                        # Add to starboard
                        starboard.set([str(message.id)])

                        try:
                            await channel.send(
                                embed=(await build_starboard_embed(message)))
                        except discord.errors.Forbidden:
                            pass
예제 #7
0
async def on_member_join(member: discord.Member, **kargs: Any) -> None:

    client: discord.Client = kargs["client"]
    ramfs: lexdpyk.ram_filesystem = kargs["ramfs"]

    inc_statistics_better(member.guild.id, "on-member-join", kargs["kernel_ramfs"])

    notifier_cache = load_message_config(member.guild.id, ramfs, datatypes=join_notifier)

    issues: List[str] = []

    # Handle notifer logging
    if member.id in notifier_cache["notifier-log-users"]:
        issues.append("User")
    if abs(discord_datetime_now().timestamp() - member.created_at.timestamp()) < int(notifier_cache["notifier-log-timestamp"]):
        issues.append("Timestamp")
    if int(notifier_cache["notifier-log-defaultpfp"]) and has_default_avatar(member):
        issues.append("Default pfp")

    if issues:
        asyncio.create_task(notify_problem(member, issues, notifier_cache["regex-notifier-log"], client, ramfs))

    joinlog = load_message_config(member.guild.id, ramfs, datatypes=join_leave_user_logs)["join-log"]

    # Handle join logs
    if joinlog and (logging_channel := client.get_channel(int(joinlog))):

        embed = discord.Embed(title=f"{member} joined.", description=f"*{member.mention} joined the server.*", color=load_embed_color(member.guild, embed_colors.creation, ramfs))
        embed.set_thumbnail(url=user_avatar_url(member))

        embed.timestamp = ts = datetime_now()
        embed.set_footer(text=f"uid: {member.id}, unix: {int(ts.timestamp())}")

        embed.add_field(name="Created", value=parsedate(member.created_at), inline=True)

        if isinstance(logging_channel, discord.TextChannel):
            asyncio.create_task(catch_logging_error(logging_channel, embed))
예제 #8
0
async def on_member_remove(member: discord.Member, **kargs: Any) -> None:

    inc_statistics_better(member.guild.id, "on-member-remove", kargs["kernel_ramfs"])

    log_channels = load_message_config(member.guild.id, kargs["ramfs"], datatypes=join_leave_user_logs)

    # Try for leave-log, default to join-log
    if (joinlog := (log_channels["leave-log"] or log_channels["join-log"])):
        if logging_channel := kargs["client"].get_channel(int(joinlog)):

            # Only run if in a TextChannel
            if not isinstance(logging_channel, discord.TextChannel):
                return

            embed = discord.Embed(title=f"{member} left.", description=f"*{member.mention} left the server.*", color=load_embed_color(member.guild, embed_colors.deletion, kargs["ramfs"]))
            embed.set_thumbnail(url=user_avatar_url(member))

            embed.timestamp = ts = datetime_now()
            embed.set_footer(text=f"uid: {member.id}, unix: {int(ts.timestamp())}")

            embed.add_field(name="Created", value=parsedate(member.created_at), inline=True)
            embed.add_field(name="Joined", value=parsedate(member.joined_at), inline=True)

            await catch_logging_error(logging_channel, embed)
예제 #9
0
async def on_message(message: discord.Message, **kargs: Any) -> None:

    client: discord.Client = kargs["client"]
    ramfs: lexdpyk.ram_filesystem = kargs["ramfs"]
    kernel_ramfs: lexdpyk.ram_filesystem = kargs["kernel_ramfs"]
    main_version_info: str = kargs["kernel_version"]
    bot_start_time: float = kargs["bot_start"]

    command_modules: List[lexdpyk.cmd_module]
    command_modules_dict: lexdpyk.cmd_modules_dict

    command_modules, command_modules_dict = kargs["command_modules"]

    # Statistics.
    stats: Dict[str, int] = {"start": round(time.time() * 100000)}

    if parse_skip_message(client, message):
        return
    elif not message.guild:
        return

    inc_statistics_better(message.guild.id, "on-message", kernel_ramfs)

    # Load message conf
    stats["start-load-blacklist"] = round(time.time() * 100000)
    mconf = load_message_config(message.guild.id, ramfs)
    stats["end-load-blacklist"] = round(time.time() * 100000)

    # Check message against automod
    stats["start-automod"] = round(time.time() * 100000)

    spammer, spamstr = antispam_check(message, ramfs, mconf["antispam"], mconf["char-antispam"])

    message_deleted: bool = False

    command_ctx = CommandCtx(
        stats=stats,
        cmds=command_modules,
        ramfs=ramfs,
        bot_start=bot_start_time,
        dlibs=kargs["dynamiclib_modules"][0],
        main_version=main_version_info,
        kernel_ramfs=kargs["kernel_ramfs"],
        conf_cache=mconf,
        verbose=True,
        cmds_dict=command_modules_dict,
        automod=False
        )

    automod_ctx = pycopy.copy(command_ctx)
    automod_ctx.verbose = False
    automod_ctx.automod = True

    # If blacklist broken generate infraction
    broke_blacklist, notify, infraction_type = parse_blacklist((message, mconf, ramfs), )
    if broke_blacklist:
        message_deleted = True
        asyncio.create_task(attempt_message_delete(message))
        execargs = [str(message.author.id), "[AUTOMOD]", ", ".join(infraction_type), "Blacklist"]
        asyncio.create_task(CallCtx(command_modules_dict[mconf["blacklist-action"]]['execute'])(message, execargs, client, automod_ctx))

    if spammer:
        message_deleted = True
        asyncio.create_task(attempt_message_delete(message))
        with db_hlapi(message.guild.id) as db:
            if not db.is_muted(userid=message.author.id):
                execargs = [str(message.author.id), mconf["antispam-time"], "[AUTOMOD]", spamstr]
                asyncio.create_task(CallCtx(command_modules_dict["mute"]["execute"])(message, execargs, client, automod_ctx))

    if notify:
        asyncio.create_task(grab_an_adult(message, message.guild, client, mconf, ramfs))

    stats["end-automod"] = round(time.time() * 100000)

    # Log files if not deleted
    if not message_deleted:
        asyncio.create_task(log_message_files(message, kernel_ramfs))

    # Check if this is meant for us.
    if not (message.content.startswith(mconf["prefix"])) or message_deleted:
        if client.user.mentioned_in(message) and str(client.user.id) == message.content.strip("<@!>"):
            try:
                await message.channel.send(f"My prefix for this guild is {mconf['prefix']}")
            except discord.errors.Forbidden:
                pass  # Nothing we can do if we lack perms to speak
        return

    # Split into cmds and arguments.
    arguments = message.content.split()
    command = arguments[0][len(mconf["prefix"]):]

    # Remove command from the arguments.
    del arguments[0]

    # Process commands
    if command in command_modules_dict:

        if "alias" in command_modules_dict[command]:  # Do alias mapping
            command = command_modules_dict[command]["alias"]

        cmd = SonnetCommand(command_modules_dict[command])

        if not await parse_permissions(message, mconf, cmd.permission):
            return  # Return on no perms

        try:
            stats["end"] = round(time.time() * 100000)

            try:
                await cmd.execute_ctx(message, arguments, client, command_ctx)
            except lib_sonnetcommands.CommandError as ce:
                try:
                    await message.channel.send(ce)
                except discord.errors.Forbidden:
                    pass

            # Regenerate cache
            if cmd.cache in ["purge", "regenerate"]:
                for i in ["caches", "regex"]:
                    try:
                        ramfs.rmdir(f"{message.guild.id}/{i}")
                    except FileNotFoundError:
                        pass

            elif cmd.cache.startswith("direct:"):
                for i in cmd.cache[len('direct:'):].split(";"):
                    try:
                        if i.startswith("(d)"):
                            ramfs.rmdir(f"{message.guild.id}/{i[3:]}")
                        elif i.startswith("(f)"):
                            ramfs.remove_f(f"{message.guild.id}/{i[3:]}")
                        else:
                            raise RuntimeError("Cache directive is invalid")
                    except FileNotFoundError:
                        pass

        except discord.errors.Forbidden as e:

            try:
                await message.channel.send(f"ERROR: Encountered a uncaught permission error while processing {command}")
                terr = True
            except discord.errors.Forbidden:
                terr = False  # Nothing we can do if we lack perms to speak

            if terr:  # If the error was not caused by message send perms then raise
                raise e

        except Exception as e:
            try:
                await message.channel.send(f"FATAL ERROR: uncaught exception while processing {command}")
            except discord.errors.Forbidden:
                pass
            raise e
예제 #10
0
async def on_guild_join(guild: discord.Guild, **kargs: Any) -> None:
    inc_statistics_better(guild.id, "on-guild-join", kargs["kernel_ramfs"])
    with db_hlapi(guild.id) as db:
        db.create_guild_db()