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))
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)
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))
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))
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
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)
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))
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" )
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)
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)