Esempio n. 1
0
def misc(context):
    warehouse = load_saved(guild=context.user_data.get('guild', ''))
    hours = 5
    responses = []
    now = datetime.datetime.utcnow()
    if (misc := warehouse.get('misc', {})) and (
            age := now - misc['timestamp']) < datetime.timedelta(hours=hours):
        try:
            with open('auctionprices.dict',
                      'rb') as ap, open('stockprices.dict', 'rb') as sp:
                auction_prices = pickle.load(ap)
                stock_prices = pickle.load(sp)
                price_age = min(auction_prices['last_update'],
                                stock_prices['last_update'])
                prices = {**auction_prices, **stock_prices}
                prices['last_update'] = price_age
        except FileNotFoundError:
            prices = {}

        output = [
            f'Based on /g_stock_misc data {age.seconds // 60} minutes old:\n'
        ]
        for id in sorted(misc['data'], key=misc['data'].get, reverse=True):
            price = prices.get(id, '')
            if price:
                currency = '💰' if isinstance(price, int) else '�'
                price = f'{currency}{price}'

            output.append(
                f'<code>{id}</code> {id_lookup.get(id, {}).get("name", "Name Missing")} x {misc["data"][id]} {price}'
            )

        sort_by_weight = {
            id: misc['data'][id] * id_lookup.get(id, {}).get('weight', 1)
            for id in misc['data']
        }
        sort_by_weight = sorted(sort_by_weight,
                                key=sort_by_weight.get,
                                reverse=True)
        x = [
            misc['data'][id] * id_lookup.get(id, {}).get('weight', 1)
            for id in sort_by_weight
        ]
        y = [
            f'{id_lookup.get(id, {}).get("name", "??").lower()} {id}'
            for id in sort_by_weight
        ]
        r = range(len(sort_by_weight))
        plt.clf()  # clear plot, because it doesn't get cleared from last run
        plt.figure(figsize=(6.4, 1.5 + (len(sort_by_weight) * 0.15)))
        plt.barh(r, x)
        plt.yticks(r, y, fontsize='8')
        plt.legend(loc='upper right', labels=['Weight'])
        plt.subplots_adjust(left=0.3)
        buf = io.BytesIO()
        plt.savefig(buf, format='png')
        buf.seek(0)
        responses.append(buf)

        responses.append('\n'.join(output))
Esempio n. 2
0
def other(context):
    warehouse = load_saved(guild=context.user_data.get('guild', ''))
    hours = 3
    responses = []
    now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
    if (other := warehouse.get('other', {})) and (age := now - other['timestamp']) < datetime.timedelta(hours=hours):
        output = [f'Based on /g_stock_other data {age.seconds // 60} minutes old:\n']
        page_counter = 0
        value_to_weight = {id: id_lookup.get(id, {}).get('shopSellPrice', 0)/id_lookup.get(id, {}).get('weight', 1) for id in other['data']}
        for id in sorted(value_to_weight, key=value_to_weight.get, reverse=True):
            items = other['data'][id]
            hold_output = []
            sub_output = []
            item_count = 0
            for item in sorted(items):
                item_id, name, count = item
                if item_id.startswith('u') and (match := re.search(r'[╦брхЃрхЄрХюрхѕрхЅ]+', item_id)):
                    split = match.span()[0]
                    item_id = item_id[:split] + 'РђІ' + item_id[split:]  # zero width space
                item_count += count
                sub_output.append(f'<code>  </code>/g_i_{item_id} {name} x {count}')
            hold_output.append(f"<code>{id}</code> {id_lookup.get(id, {}).get('name', 'Assorted')} РѕЉ {item_count} ­Ъњ░{id_lookup.get(id, {}).get('shopSellPrice', '??')}")
            hold_output.append(f"­Ъњ░/Рџќ№ИЈ {value_to_weight[id]:.2f}")
            hold_output.extend(sub_output)
            hold_output.append(' ')
            hold_output = '\n'.join(hold_output)

            page_counter += len(hold_output.encode('utf-8'))
            if page_counter >= 3000:  # tg officially supports messages as long as 4096, but the formatting gives up around 3000
                responses.append('\n'.join(output))
                page_counter = 0
                output = []
            output.append(hold_output)
Esempio n. 3
0
def alch(context):
    warehouse = load_saved(guild=context.user_data.get('guild', ''))
    hours = 1.5
    responses = []
    now = datetime.datetime.utcnow()
    if (alch := warehouse.get('alch', {})) and (age := now - alch['timestamp']) < datetime.timedelta(hours=hours):
        try:
            with open('stockprices.dict', 'rb') as fp:
                prices = pickle.load(fp)
        except FileNotFoundError:
            prices = {}

        output = [f'Based on /g_stock_alch data {age.seconds // 60} minutes old:\n']
        for x in range(39, 70):
            if f'{x:02}' in id_lookup:
                alch['data'][f'{x:02}'] = alch['data'].get(f'{x:02}', 0)

        for id in sorted(alch['data'], key=alch['data'].get, reverse=True):
            price = prices.get(id, '')
            if price:
                price = f'💰{price}'
            output.append(f'<code>{id}</code> {id_lookup[id]["name"]} x {alch["data"][id]} {price}')
        if prices:
            output.append(f"\nPrices no fresher than {(now - prices['last_update']).seconds // 60} minutes.")

        sort_by_count = sorted(alch['data'], key=alch['data'].get, reverse=True)
        x = [alch['data'][id] for id in sort_by_count]
        y = [f'{id_lookup[id]["name"].lower()} {id}' for id in sort_by_count]
        r = range(len(x))
        plt.clf()  # clear plot, because it doesn't get cleared from last run
        plt.figure(figsize=(6.4, 1.5+(len(sort_by_count)*0.15)))
        plt.barh(r, x)
        plt.yticks(r, y, fontsize='8')
        plt.legend(loc='upper right', labels=['Alch Count'])
        plt.subplots_adjust(left=0.3)
        buf = io.BytesIO()
        plt.savefig(buf, format='png')
        buf.seek(0)
        responses.append(buf)

        responses.append('\n'.join(output))
Esempio n. 4
0
def stock(context):
    warehouse = load_saved(guild=context.user_data.get('guild', ''))
    hours = 1.5
    responses = []
    now = datetime.datetime.utcnow()
    if (res := warehouse.get('res', {})) and (
            age := now - res['timestamp']) < datetime.timedelta(hours=hours):
        try:
            with open('stockprices.dict', 'rb') as fp:
                prices = pickle.load(fp)
        except FileNotFoundError:
            prices = {}

        output = [
            f'Based on /g_stock_res data {age.seconds // 60} minutes old:\n⚖�'
        ]
        for x in range(1, 39):
            if f'{x:02}' in id_lookup:
                res['data'][f'{x:02}'] = res['data'].get(f'{x:02}', 0)

        for id in sorted(res['data'], key=res['data'].get, reverse=True):
            trade = '✅' if id_lookup[id]['exchange'] else '�'
            price = prices.get(id, '')
            if price:
                price = f'💰{price}'
            output.append(
                f'{trade}<code>{id}</code> {id_lookup[id]["name"]} x {res["data"][id]} {price}'
            )
        if prices:
            output.append(
                f"\nPrices no fresher than {(now - prices['last_update']).seconds // 60} minutes."
            )

        sort_by_weight = {
            id: res['data'][id] * id_lookup[id]['weight']
            for id in res['data']
        }
        sort_by_weight = sorted(sort_by_weight,
                                key=sort_by_weight.get,
                                reverse=True)
        x = [[res['data'][id] for id in sort_by_weight],
             [
                 res['data'][id] * (id_lookup[id]['weight'] - 1)
                 if id_lookup[id]['weight'] == 2 else 0
                 for id in sort_by_weight
             ],
             [
                 res['data'][id] * (id_lookup[id]['weight'] - 1)
                 if id_lookup[id]['weight'] >= 3 else 0
                 for id in sort_by_weight
             ]]
        r = range(len(sort_by_weight))
        plt.clf()  # clear plot, because it doesn't get cleared from last run
        plt.figure(figsize=(6.4, 1.5 + (len(sort_by_weight) * 0.15)))
        plt.barh(r, x[0])
        plt.barh(r, x[1], left=x[0],
                 color=(1, .6, 0))  # some color between yellow and orange
        plt.barh(r, x[2], left=x[0], color='red')
        plt.yticks(
            r,
            [f'{id_lookup[id]["name"].lower()} {id}' for id in sort_by_weight],
            fontsize='8')
        plt.legend(loc='upper right',
                   labels=['Stock Count', 'Double Weight', 'Triple Weight'])
        plt.subplots_adjust(left=0.3)
        buf = io.BytesIO()
        plt.savefig(buf, format='png')
        buf.seek(0)
        responses.append(buf)

        responses.append('\n'.join(output))
Esempio n. 5
0
def parts(matches, guild):
    """builds withdraw commands.
       expects an iterator of dicts with one key named 'number' and the other named 'id' or 'name'"""
    warehouse = load_saved(guild)
    hours = 2.5
    now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
    command_suffixes = set()

    matches = list(matches)
    for match in matches:
        if match.get('name'):
            match['id'] = name_lookup[match['name'].strip().lower().replace(
                '📃', '').replace('🧩', '')]['id']

        if match['id'][0].isdigit():
            if int(match['id']) <= 38:
                command_suffixes.add('res')
            else:
                command_suffixes.add('alch')
        elif match['id'][0] in 'sp':
            command_suffixes.add('misc')
        elif match['id'][0] == 'r':
            command_suffixes.add('rec')
        elif match['id'][0] == 'k':
            command_suffixes.add('parts')
        elif match['id'][0] in 'wuea':
            command_suffixes.add('other')

    notice = ''
    guild_stock = {}
    still_missing = set()
    oldest = now - now
    for suffix in command_suffixes:
        suf = warehouse.get(suffix, {})
        if suf:
            age = now - suf['timestamp']
            if age < datetime.timedelta(hours=hours):
                guild_stock.update(suf['data'])
                oldest = max(oldest, age)
            else:
                still_missing.add(suffix)
        else:
            guild_stock = {}
            break

    if still_missing:
        for suffix in sorted(still_missing):
            notice += f'\n/g_stock_{suffix}'

    have = []
    missing = []
    for d in matches:
        d["number"] = d["number"] if d["number"] else "1"

        if guild_stock:
            diff = int(d["number"]) - guild_stock.get(d['id'], 0)
            if diff > 0:
                if d['id'][0] not in 'rk':
                    exchange = False
                    if id_lookup[d['id']].get('exchange', False):
                        exchange = True
                        missing.append(
                            f"<code>/wtb_{d['id']}_{diff}</code> {id_lookup[d['id']]['name']}"
                        )
                    if id_lookup[d['id']].get('craftable', False):
                        craftcmd = f"/craft_{d['id']}_{diff} {id_lookup[d['id']]['name']}"
                        if exchange:
                            missing[-1] = missing[-1].split(
                            )[0] + f' or {craftcmd}'
                        else:
                            missing.append(craftcmd)
                else:
                    missing.append(
                        f"<code>{d['id']} {diff}</code> {id_lookup[d['id']]['name']}"
                    )
                d['number'] = guild_stock.get(d['id'], 0)

        if d['number']:
            have.append(f' {d["id"]} {d["number"]} ')

    if have:
        command = ['<code>/g_withdraw']
        for n, id in enumerate(sorted(have)):
            if not (n + 1) % 10:
                command.append('</code>\n<code>/g_withdraw')
            command.append(id)
        command.append('</code>\n\n')
        command = ''.join(command)
    else:
        command = ''

    if missing:
        missing = '\n'.join([
            f"Based on guild stock state {oldest.seconds // 60} minutes old, item{'' if len(missing) == 1 else 's'} missing:"
        ] + missing + [''])
    else:
        missing = ''

    if notice:
        notice = 'Missing current guild stock state. Consider forwarding:' + notice

    return command + missing + notice
Esempio n. 6
0
def warehouse_crafting(context):
    warehouse = load_saved(guild=context.user_data.get('guild', ''))
    hours = 2.5
    responses = []
    now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
    if (rec := warehouse.get('rec', {})) and (parts := warehouse.get('parts', {})) and \
            (age_rec := now - rec['timestamp']) < datetime.timedelta(hours=hours) and \
            (age_parts := now - parts['timestamp']) < datetime.timedelta(hours=hours):
        try:
            with open('auctionprices.dict', 'rb') as fp:
                prices = pickle.load(fp)
        except FileNotFoundError:
            prices = {}

        older_command = '/g_stock_parts' if age_parts >= age_rec else '/g_stock_rec'
        output = [
            f'Based on {older_command} data {max(age_rec, age_parts).seconds // 60} minutes old:\n'
        ]
        page_counter = 0
        for n in range(1, 125):
            if 62 <= n <= 77:
                continue

            rec_id = f'r{n:02}'
            name = id_lookup[rec_id]['name'].rpartition(" ")[0]
            recipe = name_lookup.get(name.lower(), {}).get('recipe', {})
            parts_needed, part_name = '1000000', 'Part'
            for k in recipe:
                if k.startswith(name.split()[0]) and not k.endswith('Recipe'):
                    parts_needed = int(recipe.get(k))
                    part_name = k.rpartition(' ')[2]
                    part_id = name_lookup.get(k.lower(), {}).get('id')

            count_recipes = rec['data'].get(rec_id, 0)
            count_parts = parts['data'].get(part_id, 0)
            complete_parts_sets = count_parts // parts_needed
            # parts_missing_for_next_set = count_parts % parts_needed
            # recipes_missing = complete_parts_sets - count_recipes
            things_missing = int(not bool(count_recipes)) + max(
                parts_needed - count_parts, 0)
            num_craftable = min(count_recipes, complete_parts_sets)
            ready = '✅' if num_craftable else '❌'
            finished_part_id = name_lookup[name.lower()]['id']
            recipe_location = get_id_location(rec_id)
            part_location = get_id_location(part_id)
            recipe_price = ''
            part_price = ''
            if prices:
                recipe_price = str(prices.get(rec_id, ''))
                part_price = str(prices.get(part_id, ''))
                if recipe_price:
                    recipe_price = f'👝{recipe_price}'
                if part_price:
                    part_price = f'👝{part_price}'

            # Getting through this gauntlet without hitting a continue means you get displayed
            if not num_craftable and not context.args:
                continue
            if context.args and context.args[0].lower(
            ) != 'all':  # if it's 'all' then jump to after the gauntlet
                if context.args[0].isdigit() and 0 < things_missing <= int(
                        context.args[0]):
                    pass
                elif context.args[0].lower().startswith('overstock'):
                    try:
                        multiple = int(context.args[1])
                    except IndexError:
                        multiple = 2
                    if count_parts / parts_needed <= multiple and count_recipes <= multiple:
                        continue
                else:
                    try:
                        regex = re.compile(context.args[0].lower())
                        matches = regex.findall(name.lower())
                        if not matches:
                            continue
                    except re.error:
                        continue

            hold = []
            hold.append(f'{ready} {name} /c_{finished_part_id}')
            if num_craftable:
                hold.append(f'Complete sets: <code>{num_craftable}</code>')
            hold.append(
                f'{part_name}s per recipe: <code>{parts_needed}</code>')
            hold.append(
                f'<code>{rec_id}</code> Recipe{"s" if count_recipes != 1 else ""}: <code>{count_recipes}</code> {recipe_price} {recipe_location}'
            )
            hold.append(
                f'<code>{part_id}</code> {part_name}{"s" if count_parts != 1 else ""}: <code>{count_parts}</code> {part_price} {part_location}'
            )
            hold.append(' ')
            hold = '\n'.join(hold)

            page_counter += len(hold.encode('utf-8'))
            if page_counter >= 2500:  # tg officially supports messages as long as 4096, but the formatting gives up around 3000
                responses.append('\n'.join(output))
                page_counter = 0
                output = []
            output.append(hold)

        result = '\n'.join(output)
        if result:
            responses.append(result)
        if result.rstrip().endswith(':'):
            responses.append('No matches in stock')

        if context.args and context.args[0].lower() in ('all', 'chart'):
            parts_rec = {**parts['data'], **rec['data']}
            count_and_weight = {
                id: parts_rec[id] * id_lookup[id]['weight']
                for id in parts_rec
            }
            sort_by_weight = sorted(count_and_weight,
                                    key=count_and_weight.get,
                                    reverse=True)
            x = [[
                count_and_weight[id] if id[0] == 'k' else 0
                for id in sort_by_weight
            ],
                 [
                     count_and_weight[id] if id[0] == 'r' else 0
                     for id in sort_by_weight
                 ]]
            y = [
                f"{id_lookup[id]['name'].lower()} {id}"
                for id in sort_by_weight
            ]
            r = range(len(sort_by_weight))
            plt.clf(
            )  # clear plot, because it doesn't get cleared from last run
            plt.figure(figsize=(6.4, 1.5 + (len(sort_by_weight) * 0.15)))
            plt.barh(r, x[0])
            plt.barh(r, x[1], color='red')
            plt.yticks(r, y, fontsize='8')
            plt.legend(loc='upper right', labels=['Parts', 'Recipes'])
            plt.subplots_adjust(left=0.3)
            buf = io.BytesIO()
            buf.name = 'weights.pdf'
            plt.savefig(buf, format='pdf')
            buf.seek(0)
            responses.append(buf)
Esempio n. 7
0
def alch(context):
    args = ['']
    if context.args:
        args = context.args

    warehouse = load_saved(guild=context.user_data.get('guild', ''))
    hours = 1.5
    responses = []
    now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
    if (alch := warehouse.get('alch', {})) and (
            age := now - alch['timestamp']) < datetime.timedelta(hours=hours):
        try:
            with open('stockprices.dict', 'rb') as fp:
                prices = pickle.load(fp)
        except FileNotFoundError:
            prices = {}

        output = [
            f'Based on /g_stock_alch data {age.seconds // 60} minutes old:\n'
        ]
        for x in range(39, 82):
            if f'{x:02}' in id_lookup:
                alch['data'][f'{x:02}'] = alch['data'].get(f'{x:02}', 0)

        if args[0] == 'nosort':
            ordered_items = sorted(alch['data'], reverse=True)
        else:
            ordered_items = sorted(alch['data'],
                                   key=alch['data'].get,
                                   reverse=True)

        for id in ordered_items:
            price = prices.get(id, '')
            if price:
                price = f' 💰{price}'

            brewable = ''
            if id_lookup[id].get('craftable'):
                recipe = id_lookup[id]['recipe']
                fullsets = 1000000
                for name, count in recipe.items():
                    fullsets = min(
                        alch['data'].get(name_lookup[name.lower()]['id'], 0) //
                        int(count), fullsets)
                brewable = f' ⚗� /c_{id} {fullsets}'

            output.append(
                f'<code>{id}</code> {id_lookup[id]["name"]} x {alch["data"][id]}{price}{brewable}'
            )
        if prices:
            output.append(
                f"\nPrices no fresher than {(now - prices['last_update']).seconds // 60} minutes."
            )

        sort_by_count = sorted(alch['data'],
                               key=alch['data'].get,
                               reverse=True)
        x = [alch['data'][id] for id in sort_by_count]
        y = [f'{id_lookup[id]["name"].lower()} {id}' for id in sort_by_count]
        r = range(len(x))
        plt.clf()  # clear plot, because it doesn't get cleared from last run
        plt.figure(figsize=(6.4, 1.5 + (len(sort_by_count) * 0.15)))
        plt.barh(r, x)
        plt.yticks(r, y, fontsize='8')
        plt.legend(loc='upper right', labels=['Alch Count'])
        plt.subplots_adjust(left=0.3)
        buf = io.BytesIO()
        plt.savefig(buf, format='png')
        buf.seek(0)
        responses.append(buf)

        responses.append('\n'.join(output))
Esempio n. 8
0
    def warehouse_in():
        """read in /g_stock_* forwards"""
        def discover_id(testname):
            '''slow base_id finder for use only with troublesome custom named pieces of equipment'''
            lookup_names = [
                item for id, item in id_lookup.items() if id[0] in 'aw'
                and item.get('depositable') and item.get('enchantable')
            ]
            result = [
                item['id'] for item in lookup_names if item['name'] in testname
            ]
            if len(result) == 1:
                return result[0]
            return 'x'  # someone is trying to be difficult

        followup = {
            'res': '/stock',
            'alch': '/alch',
            'rec': '/warehouse',
            'parts': '/w_1',
            'other': '/other',
            'misc': '/misc'
        }
        guild = context.user_data.get('guild', '')
        if not hasattr(
                update.effective_message.forward_from,
                'id') or update.effective_message.forward_from.id not in [
                    408101137
                ]:  # @chtwrsbot
            ret.append('Must be a forward from @chtwrsbot. Try again.')
        else:
            now = update.effective_message.forward_date
            warehouse = load_saved()
            data = {}
            for row in text.split('\n')[1:]:
                if row[0] == 'u':
                    row += ' x 1'
                s = row.split()
                data[s[0]] = int(s[-1])

            id_sample = list(data.keys())[0]
            if id_sample[0].isdigit():
                if int(id_sample) <= 38:
                    key = 'res'
                else:
                    key = 'alch'
            elif id_sample[0] in 'spfc':
                key = 'misc'
            elif id_sample[0] == 'r':
                key = 'rec'
            elif id_sample[0] == 'k':
                key = 'parts'
            elif id_sample[0] in 'wuea':
                key = 'other'
                data = {}
                for row in text.split('\n')[1:]:
                    if row[0] == 'u':
                        row += ' x 1'
                    s = row.split()
                    supplied_id = s[0]
                    count = s[-1]
                    modifier = ''
                    if s[1].startswith('⚡'):
                        modifier = s.pop(1)
                    name = ' '.join(s[1:-2])
                    base_id = name_lookup.get(name.lower(), {}).get('id')
                    if not base_id:
                        base_id = discover_id(name)  # preserve case here
                    data.setdefault(base_id, []).append(
                        (supplied_id, f'{modifier} {name}', int(count)))

            if not warehouse.get(guild):
                warehouse[guild] = {}
            if not warehouse[guild].get(
                    key) or now > warehouse[guild][key].get(
                        'timestamp', datetime.datetime.min):
                warehouse[guild][key] = {'timestamp': now, 'data': data}
                with open('warehouse.dict', 'wb') as warehouseFile:
                    pickle.dump(warehouse, warehouseFile)
                ret.append(followup.get(key, key))
            else:
                ret.append(f'{key}, but not newer than data on file')
        if not guild:
            ret.append(
                "Your guild affiliation is not on file with this bot. Consider forwarding something that indicates what guild you're in. Eg: /me or /report or /hero"
            )
Esempio n. 9
0
def warehouse_data(update, context):
    """See and clear warehouse_data"""
    text = str(warehouse.load_saved())
    if context.args and context.args[0] == 'clear':
        os.remove('warehouse.dict')
    send(text, update, context)
Esempio n. 10
0
def all_stock(context):
    warehouse = load_saved(guild=context.user_data.get('guild', ''))
    hours = 50
    responses = []
    now = datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc)
    if (res := warehouse.get('res', {})) and \
       (rec := warehouse.get('rec', {})) and \
       (alch := warehouse.get('alch', {})) and \
       (misc := warehouse.get('misc', {})) and \
       (parts := warehouse.get('parts', {})) and \
       (other := warehouse.get('other', {})) and \
       (age_res := now - res['timestamp']) < datetime.timedelta(hours=hours) and \
       (age_rec := now - rec['timestamp']) < datetime.timedelta(hours=hours) and \
       (age_alch := now - alch['timestamp']) < datetime.timedelta(hours=hours) and \
       (age_misc := now - misc['timestamp']) < datetime.timedelta(hours=hours) and \
       (age_parts := now - parts['timestamp']) < datetime.timedelta(hours=hours) and \
       (age_other := now - other['timestamp']) < datetime.timedelta(hours=hours):

        ages = (
            (age_res, '/g_stock_res'),
            (age_rec, '/g_stock_rec'),
            (age_alch, '/g_stock_alch'),
            (age_misc, '/g_stock_misc'),
            (age_parts, '/g_stock_parts'),
            (age_other, '/g_stock_other')
        )
        age, command = max(ages)
        output = [f'Based on {command} data {age.seconds // 60} minutes old:\n']

        items_by_weight = {}
        items_by_category = {}
        for category in warehouse:
            if category != 'other':
                for id, count in warehouse[category]['data'].items():
                    items_by_weight[id] = count * id_lookup.get(id, {}).get('weight', 0)
                    items_by_category[id] = category
            else:
                for id in warehouse[category]['data']:
                    items_by_weight[id] = sum(list(zip(*warehouse[category]['data'][id]))[2]) * id_lookup.get(id, {}).get('weight', 0)
                    items_by_category[id] = category

        total_guild_weight = sum(items_by_weight.values())
        output.append(f'Total guild weight: {total_guild_weight}')

        sorted_by_weight = sorted(items_by_weight, key=items_by_weight.get, reverse=False)

        x = [items_by_weight[id] for id in sorted_by_weight]
        y = [f"{id_lookup.get(id, {'name': 'unidentified object'})['name'].lower()} {id}" for id in sorted_by_weight]
        r = range(len(x))
        colors = [color_lookup[items_by_category[item.rpartition(' ')[2]]] for item in y]

        plt.clf()  # clear plot, because it doesn't get cleared from last run
        fig, ax = plt.subplots()
        ax.xaxis.tick_top()
        plt.figure(figsize=(6.4, (len(x) * 0.15)))

        plt.barh(r, x, color=colors)
        plt.yticks(r, y, fontsize='8', fontname='symbola')

        plt.title('Weight in Guild Stock')
        patches = [mpatches.Patch(color=color, label=category) for category, color in color_lookup.items()]
        plt.legend(handles=patches, ncol=3, title='/g_stock_…')
        plt.subplots_adjust(left=0.3)

        buf = io.BytesIO()
        buf.name = 'weights.pdf'
        plt.savefig(buf, format='pdf')
        buf.seek(0)

        responses.append('\n'.join(output))
        responses.append(buf)