Esempio n. 1
0
async def newraid(ctx):
    # initialize priority table and unlock soft reserves
    # TODO: reinit channel
    state = GlobalState()
    # Save the ID of the user initiating the newraid. Only they will be able to set it up
    state.new_raid_user_id = ctx.author.id

    user = ctx.author
    user_dm = user.dm_channel
    if user_dm is None:
        await user.create_dm()
        user_dm = user.dm_channel

    s = NEW_RAID_SPLIT_TOKEN
    c = NEW_RAID_CANCEL_TRIGGER

    msg = (
        f"To initialize the new raid reply to me here with the extra info in the format below or '{c}' to cancel.\n"
        f"`<raid name> {s} <raid description> {s} <raid date> {s} <raid time>`\n\n"
        "Date and time should be formatted as: dd/mm/yyyy and hh:mm\n"
        "All times are assumed to be in 24h format and being server time.\n"
        "For example:\n"
        f"`Molten Core Full Clear {s} Lets clear MC again! {s} 13/02/2069 {s} 19:30`"
    )
    await user_dm.send(msg)
    await ctx.message.delete()
Esempio n. 2
0
async def help(ctx):
    state = GlobalState()
    # get pleb and admin help messages
    embed_tuple = write_help()
    channel = ctx
    change = True

    if state.last_help is not None:
        delta = datetime.now() - state.last_help

        # spam filter of 1 hour
        if delta.days == 0 and delta.seconds < 3600:
            # get user private message channel, or create if doesn't exist
            user = ctx.author
            dm_channel = user.dm_channel
            if dm_channel is None:
                await user.create_dm()
                dm_channel = user.dm_channel

            channel = dm_channel
            change = False
            await ctx.send(
                "Sliding into your DMs to give you some _personal_ help. :kissing_heart:"
            )

    if change:
        state.last_help = datetime.now()

    await channel.send(embed=embed_tuple[0])
    await channel.send(embed=embed_tuple[1])
Esempio n. 3
0
async def on_message(message):
    # Ignore messages that we sent
    if message.author.id == bot.user.id:
        return

    state = GlobalState()
    while not state.initialized:
        await asyncio.sleep(1)

    channel = message.channel
    # New raid is being set up
    if channel.type == discord.ChannelType.private:
        if state.new_raid_user_id == message.author.id:
            await process_new_raid(message)

    # We only care for our 3 channels, rest can be ignored
    elif channel.name in AUTHORIZED_CHANNELS:
        # Only request commands are allowed in the request channel
        if channel.name == REQUEST_CHANNEL_NAME:
            if message.content.startswith(f"{PREFIX}request"):
                await bot.process_commands(message)
            else:
                await message.delete()
                user = message.author
                user_dm = user.dm_channel
                if user_dm is None:
                    await user.create_dm()
                    user_dm = user.dm_channel
                await user_dm.send(
                    f"Only requests allowed in the `{REQUEST_CHANNEL_NAME}` channel"
                )
        else:
            await bot.process_commands(message)
Esempio n. 4
0
async def itemwin(ctx, *, character_name):
    character_name = character_name.strip()

    state = GlobalState()
    try:
        state.priority_table[character_name.casefold()].received_item = True
        await ctx.send(f"Congrats, {character_name}!")
    except KeyError:
        await ctx.send("I don't know this person. :frowning:")
Esempio n. 5
0
async def winners(ctx):
    state = GlobalState()
    if state.priority_table != {}:
        WINNERS = {}
        for player_name, request in state.priority_table.items():
            if request.received_item:
                WINNERS[player_name] = state.priority_table[player_name]
        table_list = build_update_table(WINNERS)
        for table in table_list:
            await ctx.send(table)
Esempio n. 6
0
def write_info():
    state = GlobalState()
    embed = discord.Embed(
        title="Loot Priority Bot",
        description=
        "Waaaaay better than writing everything by hand, wouldn't you agree?")
    embed.add_field(name="Authors", value=AUTHOR)
    embed.add_field(name="Source", value=SOURCE)
    embed.add_field(name="Invite", value=INVITE)
    embed.add_field(name="Last Booted", value=f"{state.boot_time}")
    return embed
Esempio n. 7
0
async def boss(ctx, *, boss_id):
    boss_id = boss_id.strip()

    # boss_id is either: 1 or majordomo executus
    if not boss_id:
        await ctx.send("Am I supposed to guess? What boss do you want?")
        return

    potential_loot = None
    boss_name = None
    if boss_id.isdigit():
        # Boss identified by his number
        boss_number = int(boss_id) - 1
        if boss_number not in range(len(MC_BOSS_NAMES)):
            await ctx.send(
                "Not a real number. Do I have to spell everything out?")
            return
        else:
            boss_name = MC_BOSS_NAMES[boss_number]
            potential_loot = MC_BOSS_LOOT[boss_name]
    else:
        if len(boss_id) < 3:
            await ctx.send("Wtf kinda name is that?")
            return
        # Boss identified by name
        for name in MC_BOSS_NAMES:
            if boss_id.casefold() in name:
                boss_name = name
                potential_loot = MC_BOSS_LOOT[boss_name]
                break
        else:
            await ctx.send("No boss with that name found. Type better.")
            return

    assert boss_name is not None
    assert potential_loot is not None

    state = GlobalState()
    RELEVANT_TABLE = {}
    for item_num in potential_loot.keys():
        item_name = potential_loot[item_num]
        for character_name in state.priority_table.keys():
            if state.priority_table[character_name].item.casefold(
            ) == item_name.casefold():
                RELEVANT_TABLE[character_name] = state.priority_table[
                    character_name]
    table_list = build_update_table(RELEVANT_TABLE)
    await ctx.send("**" + boss_name.title() + "**")
    for table in table_list:
        await ctx.send(table)
Esempio n. 8
0
async def update_table():
    state = GlobalState()

    if state.priority_table:
        table_list = build_update_table()
        for table, message in zip(table_list, state.table_messages):
            await message.edit(content=table)

        for i in range(len(table_list), NUM_MESSAGES_FOR_TABLE):
            await state.table_messages[i].edit(content=RESERVED_MESSAGE_TEXT)
    else:
        msg = ("Nothing to see here... yet.")
        await state.table_messages[0].edit(content=msg)
        for i in range(1, NUM_MESSAGES_FOR_TABLE):
            await state.table_messages[i].edit(content=RESERVED_MESSAGE_TEXT)
Esempio n. 9
0
async def update_status():
    state = GlobalState()

    if state.created:
        embed = discord.Embed(title=f"{state.name}",
                              description=f"{state.description}")
        embed.add_field(name="Raid Time", value=f"{state.when}", inline=False)
        embed.add_field(name="Player Requests",
                        value=f"{len(state.priority_table)}")
        embed.add_field(name="Request Status",
                        value=f"{'LOCKED' if state.lock_flag else 'OPEN'}")
    else:
        embed = discord.Embed(
            title="Nope",
            description="No raid is being tracked at the moment.")
    await state.status_message.edit(embed=embed, content="")
Esempio n. 10
0
async def on_ready():
    logger.info(f"Successfully logged in as {bot.user.name}")

    logger.info("Looking for info channel")
    info_channel = None
    for channel in bot.get_all_channels():
        if channel.name == INFO_CHANNEL_NAME:
            info_channel = channel
            break

    if info_channel is None:
        logger.error(
            f"Could not find info channel, options were: {bot.get_all_channels()} "
        )
        exit()

    state = GlobalState()
    state.info_channel = channel
    # Loading/Initializing references to the auto-updating messages
    logger.info("Initializing updating messages")
    logger.info(
        f"Status_message is: |{state.status_message}|\nTable_message is: |{state.table_messages}|"
    )
    if state.status_message is None or len(
            state.table_messages) != NUM_MESSAGES_FOR_TABLE:
        await init_update_messages(channel)
    else:
        try:
            state.status_message = await channel.fetch_message(
                state.status_message)
            for i in range(NUM_MESSAGES_FOR_TABLE):
                state.table_messages[i] = await channel.fetch_message(
                    state.table_messages[i])
        except discord.NotFound:
            logger.warning(
                "Could not find info/status messages by their saved id. Reiniting"
            )
            await init_update_messages(channel)

    await update_status()
    await update_table()
    state.initialized = True
    state.save_current_state()
Esempio n. 11
0
async def show(ctx, *, sort_by):
    sort_by = sort_by.strip()

    state = GlobalState()
    # print table of requests in private message
    if state.priority_table != {}:
        # get user private message channel, or create if doesn't exist
        user = ctx.author
        dm_channel = user.dm_channel
        if dm_channel is None:
            await user.create_dm()
            dm_channel = user.dm_channel

        await ctx.send("Sliding into your DMs. :wink:")

        table_list = build_update_table(state.priority_table, sort_by)
        for table in table_list:
            await dm_channel.send(table)

    else:
        await ctx.send("I got nothing to show you, my dude.")
Esempio n. 12
0
 async def decorated(*args, **kwargs):
     await func(*args, **kwargs)
     state = GlobalState()
     state.save_current_state()
     await update_status()
     await update_table()
Esempio n. 13
0
def build_update_table(loot_table=None, sort_by=None):
    HEADERS = ["Name", "Role", "Class", "Item"]

    if loot_table is None:
        state = GlobalState()
        loot_table = state.priority_table

    table = []
    table_list = []

    max_name_size = max(len(name) for name in loot_table.keys())
    max_item_size = max(len(request.item) for request in loot_table.values())
    max_role_size = max(len(request.role) for request in loot_table.values())
    max_class_size = max(
        len(request.wow_class) for request in loot_table.values())
    max_item_size = max(len(request.item) for request in loot_table.values())

    for key in loot_table.keys():
        request = loot_table[key].as_presentable()
        row = [
            key.title().ljust(max_name_size, " "),
            request.role.ljust(max_role_size, " "),
            request.wow_class.ljust(max_class_size, " "),
            request.item.ljust(max_item_size, " "),
        ]
        table.append(row)

    if sort_by is not None and sort_by.title() in HEADERS:
        sort_column = HEADERS.index(sort_by.title())
        ordered_table = sorted(table, key=lambda x: (x[sort_column]))
    else:
        # By default we sort by (item, name)
        ordered_table = sorted(table, key=lambda x: (x[3], x[0]))

    row_count = len(ordered_table)
    rows_per_msg = 10
    i = 0
    if row_count > rows_per_msg:
        table_list.append("```" + tb.tabulate(
            ordered_table[(rows_per_msg * i):(rows_per_msg * (i + 1))],
            headers=HEADERS,
            tablefmt="fancy_grid",
        ) + "```")
        row_count -= rows_per_msg
        i += 1
    else:
        table_list.append("```" + tb.tabulate(
            ordered_table[(rows_per_msg * i):],
            headers=HEADERS,
            tablefmt="fancy_grid",
        ) + "```")
        return table_list

    while row_count > rows_per_msg:
        table_list.append("```" + tb.tabulate(
            ordered_table[(rows_per_msg * i):(rows_per_msg * (i + 1))],
            tablefmt="fancy_grid",
        ) + "```")
        row_count -= rows_per_msg
        i += 1
    table_list.append("```" + tb.tabulate(
        ordered_table[(rows_per_msg * i):],
        tablefmt="fancy_grid",
    ) + "```")

    return table_list
Esempio n. 14
0
async def unlock(ctx):
    # lower flag for more soft reserves
    GlobalState().lock_flag = False
    await ctx.send("Raid priority is open once more!")
Esempio n. 15
0
async def lock(ctx):
    # raise flag for no more soft reserves
    GlobalState().lock_flag = True
    await ctx.send("Raid priority is now locked!")
Esempio n. 16
0
async def init_update_messages(channel):
    state = GlobalState()
    state.table_messages = []
    state.status_message = await channel.send("I'm bootin' baby!")
    for i in range(NUM_MESSAGES_FOR_TABLE):
        state.table_messages.append(await channel.send(RESERVED_MESSAGE_TEXT))
Esempio n. 17
0
async def request(ctx, *, request):
    request = request.strip()
    if ctx.channel.name != REQUEST_CHANNEL_NAME:
        await ctx.send(
            f"Requests can only be done in the `{REQUEST_CHANNEL_NAME}` channel. Now begone."
        )
        return

    state = GlobalState()
    if state.created is None:
        await ctx.send(
            f"There is no raid currently being tracked. Complain to an {ADMIN_ROLE} until something happens."
        )
        return

    if state.lock_flag:
        await ctx.send("Raid loot reservation is currently locked.")
        return

    user = ctx.author
    user_dm = user.dm_channel
    user_message = ctx.message
    if user_dm is None:
        await user.create_dm()
        user_dm = user.dm_channel

    if request.count("/") != 3:
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: / count"))
        await user_dm.send((
            f"You sent: `{user_message.content}`\n\n"
            "It looks like you forgot something.:thinking:\n"
            f"Fix your command (and your life) and message me again in the `{REQUEST_CHANNEL_NAME}` channel!\n"
        ))
        await user_message.delete()
        return

    name, role, wow_class, item = [
        info.strip().casefold() for info in request.split("/")
    ]
    if min(len(role), len(wow_class), len(item), len(name)) < 3:
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: len check"))
        await user_dm.send((
            f"You sent: `{user_message.content}`\n\n"
            "I don't speak whatever this is. Write things out properly - and spell them right ffs.\n"
            f"Fix your command (and your life) and message me again in the `{REQUEST_CHANNEL_NAME}` channel!"
        ))
        await user_message.delete()
        return

    for existing_role in WOW_ROLES:
        if existing_role.startswith(role):
            role = existing_role
            break
    else:
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: role check"))
        await user_dm.send((
            f"You sent: `{user_message.content}`\n\n"
            "Couldn't understand your declared role.\n"
            "Format is: `>request <name>/<role>/<class>/<item>`\n"
            f"Your role choices are: {' | '.join(WOW_ROLES)}\n"
            f"Fix your command (and your life) and message me again in the `{REQUEST_CHANNEL_NAME}` channel!"
        ))
        await user_message.delete()
        return

    for existing_class in WOW_CLASSES:
        if existing_class.startswith(wow_class):
            wow_class = existing_class
            break
    else:
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: class check"))
        await user_dm.send((
            f"You sent: `{user_message.content}`\n\n"
            "Couldn't understand your declared class.\n"
            "Format is: `>request <name>/<role>/<class>/<item>`\n"
            f"Your classes choices are: {' | '.join(WOW_CLASSES)}\n"
            f"Fix your command (and your life) and message me again in the `{REQUEST_CHANNEL_NAME}` channel!"
        ))
        await user_message.delete()
        return

    try:
        search = OpenSearch('item', item)
    except OpenSearchError as e:
        logger.warning(e)
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: item exists check"))
        await user_dm.send((
            f"You sent: `{user_message.content}`\n\n"
            "Could not find any matching items.\n"
            f"Figure your shit out and message me again in the `{REQUEST_CHANNEL_NAME}` channel!"
        ))
        await user_message.delete()
        return
    except JSONDecodeError:
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: wowhead is being a dick"))
        await user_dm.send((
            f"You sent: `{user_message.content}`\n\n"
            "Could not properly fetch data from wowhead.\n"
            "Wowhead is probably just feeling like being a dick at the moment - try the request again in a little while.\n"
            "If problems continue then give Frawni or Malzo a poke!"))
        await user_message.delete()
        return

    # Valid item is one that we found on WoWhead and that is also part of our loot table
    valid_item = None
    for item in search.results:
        for boss in MC_BOSS_LOOT:
            if item.id in MC_BOSS_LOOT[boss]:
                valid_item = item
                break
        else:
            # If we didnt break out of inner loop, continue with outer
            continue
        # If we did break out of inner loop, break out of the outer too
        break

    if valid_item is None:
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: valid item check"))
        await user_dm.send((
            f"You sent: `{user_message.content}`\n\n"
            "Look, I agree, that is an item. You also have _zero_ chance of getting it doing this raid.\n"
            f"Figure your shit out and message me again in the `{REQUEST_CHANNEL_NAME}` channel!"
        ))
        await user_message.delete()
        return

    request = Request(role=role,
                      wow_class=wow_class,
                      item=item.name,
                      datetime=datetime.now(),
                      received_item=False)
    state.priority_table[name] = request
    request = request.as_presentable()
    await ctx.send((f"Noting down [{item.name}] for {name.title()}.\n"
                    f"Class: {request.wow_class} \t Role: {request.role}\n"))

    try:
        item.get_tooltip_data()
    except SearchObjectError:
        logger.warning(
            (f"Failed loot request from |{user_message.author.display_name}|. "
             f"Messaged: |{user_message.content}|. "
             "Reason: tooltip broke"))
        logger.exception("Failed to get tooltip data")
        await user_dm.send(
            "Well, the tooltip generation f****d up. There's a limit to how much I can fix someone else's code."
        )
    else:
        with BytesIO() as image_file:
            item.image.save(image_file, format="png")
            image_file.seek(0)
            await user_dm.send(
                file=discord.File(image_file, filename="reserved.png"))
Esempio n. 18
0
async def process_new_raid(message):
    state = GlobalState()
    user_dm = message.channel
    raid_info = message.content

    if raid_info.strip().lower() == "cancel":
        state.new_raid_user_id = None
        await user_dm.send("Cancelling new raid. No changes have been made.")
        return

    if message.content.count(NEW_RAID_SPLIT_TOKEN) != 3:
        await user_dm.send(
            f"Not enough information supplied. You need to give 4 things split by 3 {NEW_RAID_SPLIT_TOKEN}"
        )
        return

    raid_name, raid_desc, raid_date, raid_time = [
        info.strip() for info in message.content.split(NEW_RAID_SPLIT_TOKEN)
    ]
    if raid_time.count(":") != 1 or raid_date.count("/") != 2:
        await user_dm.send(
            "Your date and/or time is not formatted properly. Are you sure you are using '/' and ':'?"
        )
        return

    # Make sure both inputs are 0-padded digits for the datetime parser
    raid_date = "/".join([
        part.strip().rjust(
            2, "0")  # All digits must be 2 chars for datetime parse
        if idx != 2 else part.strip().rjust(
            4, "0")  # Year needs to be padded to 4
        for idx, part in enumerate(raid_date.split("/"))
    ])
    raid_time = ":".join(
        [part.strip().rjust(2, "0") for part in raid_time.split(":")])
    try:
        raid_datetime = datetime.strptime(f"{raid_date} {raid_time}",
                                          "%d/%m/%Y %H:%M")
    except ValueError as e:
        print(e)
        await user_dm.send(
            "Your date and/or time is completely f****d. What did you do?")
        return
    await user_dm.send("All good! Initializing new raid.")
    state.new_raid(name=raid_name.strip(),
                   description=raid_desc.strip(),
                   when=raid_datetime)
    await user_dm.send("Saving new state.")
    state.save_current_state()
    await user_dm.send(f"Wiping `{INFO_CHANNEL_NAME}` channel.")
    bots_messages = [msg.id for msg in state.table_messages
                     ] + [state.status_message.id]
    async for message in state.info_channel.history(limit=None):
        if message.id not in bots_messages:
            await message.delete()

    await user_dm.send(
        f"Updating banners in the `{INFO_CHANNEL_NAME}` channel.")
    await update_table()
    await update_status()
    await user_dm.send(
        f"Announcing new raid in `{REQUEST_CHANNEL_NAME}` channel.")

    logger.info("Looking for request channel for new raid announcement")
    request_channel = None
    for channel in bot.get_all_channels():
        if channel.name == REQUEST_CHANNEL_NAME:
            request_channel = channel
            break
    if request_channel:
        await request_channel.send("New raid is now live!")
    else:
        await user_dm.send(
            "Could not find the request channel so continuing with no announcement."
        )

    await user_dm.send("New raid ready to go!")
Esempio n. 19
0
            await ctx.send(table)


if __name__ == "__main__":
    logging.basicConfig(
        filename=f"{path.dirname(path.abspath(__file__))}/lootbot.log",
        level=logging.INFO,
        filemode='a',
        format="%(levelname)s:%(name)s:[%(asctime)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S")

    logging.info(" ")
    logging.info("-" * 50)
    logging.info(" ")

    GlobalState().load_current_saved_state()
    GlobalState().boot_time = datetime.now()

    try:
        logger.info("Starting the endless loop")
        boot_count = 0
        while True:
            boot_count += 1
            logger.info(f"!!! Running the bot - {boot_count} !!!")
            bot.run(DISCORD_TOKEN)
            logger.info("Bot returned - rerunning in loop")
    except RuntimeError:
        logger.exception(f"Exiting messily. Boot count: {boot_count}")
    except Exception:
        logger.exception(f"Lets see what hides here! Boot count: {boot_count}")