246 lines
9.4 KiB
Python
246 lines
9.4 KiB
Python
import asyncio
|
|
import discord
|
|
from tabulate import tabulate
|
|
from redbot.core.utils.chat_formatting import box
|
|
|
|
|
|
class ShopMenu:
|
|
def __init__(self, ctx, origin, mode=0, sorting="price"):
|
|
self.ctx = ctx
|
|
self.origin = origin
|
|
self.shop = None
|
|
self.user = None
|
|
self.enabled = True
|
|
self.mode = mode
|
|
self.sorting = sorting
|
|
|
|
async def display(self):
|
|
data = self.origin
|
|
msg, groups, page, maximum = await self.setup(data)
|
|
try:
|
|
item = await self.menu_loop(data, groups, page, maximum, msg)
|
|
except asyncio.TimeoutError:
|
|
await msg.delete()
|
|
await self.ctx.send("No response. Menu exited.")
|
|
raise RuntimeError
|
|
except MenuExit:
|
|
await msg.delete()
|
|
await self.ctx.send("Exited menu.")
|
|
raise RuntimeError
|
|
else:
|
|
if self.mode == 0:
|
|
return self.shop, item
|
|
else:
|
|
return self.user, item
|
|
|
|
async def setup(self, data=None, msg=None):
|
|
if data is None:
|
|
data = self.origin
|
|
if (self.shop is None and self.mode == 0) or (self.user is None and self.mode == 1):
|
|
data = await self.parse_data(data)
|
|
|
|
groups = self.group_data(data)
|
|
page, maximum = 0, len(groups) - 1
|
|
e = await self.build_menu(groups, page)
|
|
|
|
if msg is None:
|
|
msg = await self.ctx.send(self.ctx.author.mention, embed=e)
|
|
else:
|
|
await msg.edit(embed=e)
|
|
return msg, groups, page, maximum
|
|
|
|
async def menu_loop(self, data, groups, page, maximum, msg):
|
|
while True:
|
|
check = MenuCheck(self.ctx, groups, page, maximum)
|
|
choice = await self.ctx.bot.wait_for("message", timeout=35.0, check=check.predicate)
|
|
if choice.content.isdigit() and int(choice.content) in range(1, len(groups[page]) + 1):
|
|
selection = groups[page][int(choice.content) - 1]
|
|
try:
|
|
await choice.delete()
|
|
except (discord.NotFound, discord.Forbidden):
|
|
pass
|
|
if self.mode == 0:
|
|
item = await self.next_menu(data, selection, msg)
|
|
if not self.enabled:
|
|
try:
|
|
await msg.delete()
|
|
except (discord.NotFound, discord.Forbidden):
|
|
pass
|
|
return item
|
|
else:
|
|
pending_id = await self.pending_menu(data, selection, msg)
|
|
if not self.enabled:
|
|
try:
|
|
await msg.delete()
|
|
except (discord.NotFound, discord.Forbidden):
|
|
pass
|
|
return pending_id
|
|
if choice.content.lower() in (">", "n", "next"):
|
|
page += 1
|
|
elif choice.content.lower() in ("b", "<", "back"):
|
|
page -= 1
|
|
elif choice.content.lower() in ("p", "prev"):
|
|
if (self.shop and self.mode == 0) or (self.user and self.mode == 1):
|
|
try:
|
|
await choice.delete()
|
|
except (discord.NotFound, discord.Forbidden):
|
|
pass
|
|
if self.mode == 0:
|
|
self.shop = None
|
|
else:
|
|
self.user = None
|
|
break
|
|
pass
|
|
elif choice.content.lower() in ("e", "x", "exit"):
|
|
try:
|
|
await choice.delete()
|
|
except (discord.NotFound, discord.Forbidden):
|
|
pass
|
|
raise MenuExit
|
|
|
|
try:
|
|
await choice.delete()
|
|
except discord.NotFound:
|
|
msg, groups, page, maximum = await self.setup(msg=msg)
|
|
except discord.Forbidden:
|
|
pass
|
|
embed = await self.build_menu(groups, page=page)
|
|
await msg.edit(embed=embed)
|
|
|
|
async def parse_data(self, data):
|
|
if self.shop is None and self.mode == 0:
|
|
perms = self.ctx.author.guild_permissions.administrator
|
|
author_roles = [r.name for r in self.ctx.author.roles]
|
|
return [x for x, y in data.items() if (y["Role"] in author_roles or perms) and y["Items"]]
|
|
else:
|
|
try:
|
|
return list(data.items())
|
|
except AttributeError:
|
|
return data
|
|
|
|
async def build_menu(self, groups, page):
|
|
footer = "You are viewing page {} of {}.".format(page + 1 if page > 0 else 1, len(groups))
|
|
if self.shop is None and self.mode == 0:
|
|
output = ["{} - {}".format(idx, ele) for idx, ele in enumerate(groups[page], 1)]
|
|
elif self.mode == 0:
|
|
header = f"{'#':<3} {'Name':<29} {'Qty':<7} {'Cost':<8}\n{'--':<3} {'-'*29:<29} {'-'*4:<7} {'-'*8:<8}"
|
|
fmt = [header]
|
|
for idx, x in enumerate(groups[page], 1):
|
|
line_one = f"{f'{idx}.': <{3}} {x[0]: <{29}s} {x[1]['Qty']:<{8}}{x[1]['Cost']: < {7}}"
|
|
fmt.append(line_one)
|
|
fmt.append(f'< {x[1]["Info"][:50]} >' if len(x[1]["Info"]) < 50 else f'< {x[1]["Info"][:47]}... >')
|
|
fmt.append("",)
|
|
output = box("\n".join(fmt), "md")
|
|
elif self.mode == 1 and self.user is None:
|
|
headers = ("#", "User", "Pending Items")
|
|
fmt = [
|
|
(idx, discord.utils.get(self.ctx.bot.users, id=int(x[0])).name, len(x[1]))
|
|
for idx, x in enumerate(groups[page], 1)
|
|
]
|
|
output = box(tabulate(fmt, headers=headers, numalign="left"), lang="md")
|
|
elif self.mode == 1:
|
|
headers = ("#", "Item", "Order ID", "Timestamp")
|
|
fmt = [(idx, x[1]["Item"], x[0], x[1]["Timestamp"]) for idx, x in enumerate(groups[page], 1)]
|
|
|
|
output = box(tabulate(fmt, headers=headers, numalign="left"), lang="md")
|
|
else:
|
|
output = None
|
|
return self.build_embed(output, footer)
|
|
|
|
def sorter(self, groups):
|
|
if self.sorting == "name":
|
|
return sorted(groups, key=lambda x: x[0])
|
|
elif self.sorting == "price":
|
|
return sorted(groups, key=lambda x: x[1]["Cost"], reverse=True)
|
|
else:
|
|
return sorted(groups, key=lambda x: x[1]["Quantity"], reverse=True)
|
|
|
|
def group_data(self, data):
|
|
grouped = []
|
|
for idx in range(0, len(data), 5):
|
|
if len(data) > 5:
|
|
grouped.append(self.sorter(data[idx : idx + 5]))
|
|
else:
|
|
if not isinstance(data, dict):
|
|
grouped.append(data)
|
|
else:
|
|
grouped.append([self.sorter(data)])
|
|
return grouped
|
|
|
|
def build_embed(self, options, footer):
|
|
instructions = (
|
|
"Type the number for your selection or one of the words below "
|
|
"for page navigation if there are multiple pages available.\n"
|
|
"Next page: Type n, next, or >\n"
|
|
"Previous page: Type b, back, or <\n"
|
|
"Return to previous menu: Type p or prev\n"
|
|
"Exit menu system: Type e, x, or exit"
|
|
)
|
|
|
|
if self.shop is None and self.mode == 0:
|
|
options = "\n".join(options)
|
|
|
|
if self.mode == 0:
|
|
title = "{}".format(self.shop if self.shop else "List of shops")
|
|
else:
|
|
title = "{} Pending".format(self.user.name if self.user else "Items")
|
|
|
|
embed = discord.Embed(color=0x5EC6FF)
|
|
embed.add_field(name=title, value=options, inline=False)
|
|
embed.set_footer(text="\n".join([instructions, footer]))
|
|
|
|
return embed
|
|
|
|
async def next_menu(self, data, selection, msg):
|
|
try:
|
|
items = data[selection]["Items"]
|
|
except TypeError:
|
|
self.enabled = False
|
|
return selection[0]
|
|
else:
|
|
self.shop = selection
|
|
new_data = await self.parse_data(items)
|
|
msg, groups, page, maximum = await self.setup(data=new_data, msg=msg)
|
|
return await self.menu_loop(new_data, groups, page, maximum, msg)
|
|
|
|
async def pending_menu(self, data, selection, msg):
|
|
try:
|
|
items = data[selection[0]]
|
|
except TypeError:
|
|
self.enabled = False
|
|
return selection[0]
|
|
else:
|
|
self.user = discord.utils.get(self.ctx.bot.users, id=int(selection[0]))
|
|
new_data = await self.parse_data(items)
|
|
msg, groups, page, maximum = await self.setup(data=new_data, msg=msg)
|
|
return await self.menu_loop(new_data, groups, page, maximum, msg)
|
|
|
|
|
|
class MenuCheck:
|
|
"""Special check class for menu.py"""
|
|
|
|
def __init__(self, ctx, data, page, maximum):
|
|
self.ctx = ctx
|
|
self.page = page
|
|
self.maximum = maximum
|
|
self.data = data
|
|
|
|
def predicate(self, m):
|
|
choices = list(map(str, range(1, len(self.data[self.page]) + 1)))
|
|
if self.ctx.author == m.author:
|
|
if m.content in choices:
|
|
return True
|
|
elif m.content.lower() in ("exit", "prev", "p", "x", "e"):
|
|
return True
|
|
elif m.content.lower() in ("n", ">", "next") and (self.page + 1) <= self.maximum:
|
|
return True
|
|
elif m.content.lower() in ("b", "<", "back") and (self.page - 1) >= 0:
|
|
return True
|
|
else:
|
|
return False
|
|
else:
|
|
return False
|
|
|
|
|
|
class MenuExit(Exception):
|
|
pass
|