354 lines
14 KiB
Python
354 lines
14 KiB
Python
import datetime
|
|
|
|
import discord
|
|
import pandas as pd
|
|
import pytz
|
|
from discord.ext.commands.cooldowns import BucketType
|
|
from rapidfuzz import fuzz
|
|
from redbot.core import bank, commands
|
|
from redbot.core.commands import parse_timedelta
|
|
from redbot.core.utils.chat_formatting import box, humanize_number, humanize_timedelta
|
|
|
|
from economytrack.abc import MixinMeta
|
|
|
|
|
|
class EconomyTrackCommands(MixinMeta):
|
|
@commands.group(aliases=["ecotrack"])
|
|
@commands.has_permissions(manage_messages=True)
|
|
@commands.guild_only()
|
|
async def economytrack(self, ctx: commands.Context):
|
|
"""Configure EconomyTrack"""
|
|
|
|
@economytrack.command()
|
|
@commands.guildowner()
|
|
@commands.guild_only()
|
|
async def togglebanktrack(self, ctx: commands.Context):
|
|
"""Enable/Disable economy tracking for this server"""
|
|
async with self.config.guild(ctx.guild).all() as conf:
|
|
if conf["enabled"]:
|
|
conf["enabled"] = False
|
|
await ctx.send("Economy tracking has been **Disabled**")
|
|
else:
|
|
conf["enabled"] = True
|
|
await ctx.send("Economy tracking has been **Enabled**")
|
|
|
|
@economytrack.command()
|
|
@commands.guildowner()
|
|
@commands.guild_only()
|
|
async def togglemembertrack(self, ctx: commands.Context):
|
|
"""Enable/Disable member tracking for this server"""
|
|
async with self.config.guild(ctx.guild).all() as conf:
|
|
if conf["member_tracking"]:
|
|
conf["member_tracking"] = False
|
|
await ctx.send("Member tracking has been **Disabled**")
|
|
else:
|
|
conf["member_tracking"] = True
|
|
await ctx.send("Member tracking has been **Enabled**")
|
|
|
|
@economytrack.command()
|
|
@commands.is_owner()
|
|
async def maxpoints(self, ctx: commands.Context, max_points: int):
|
|
"""
|
|
Set the max amount of data points the bot will store
|
|
|
|
**Arguments**
|
|
`<max_points>` Maximum amount of data points to store
|
|
|
|
The loop runs every 2 minutes, so 720 points equals 1 day
|
|
The default is 21600 (30 days)
|
|
Set to 0 to store data indefinitely (Not Recommended)
|
|
"""
|
|
await self.config.max_points.set(max_points)
|
|
await ctx.tick()
|
|
|
|
@economytrack.command()
|
|
async def timezone(self, ctx: commands.Context, timezone: str):
|
|
"""
|
|
Set your desired timezone for the graph
|
|
|
|
**Arguments**
|
|
`<timezone>` A string representing a valid timezone
|
|
|
|
**Example:** `[p]ecotrack timezone US/Eastern`
|
|
|
|
Use this command without the argument to get a huge list of valid timezones.
|
|
"""
|
|
timezone = timezone.lower()
|
|
try:
|
|
tz = pytz.timezone(timezone)
|
|
except pytz.UnknownTimeZoneError:
|
|
likely_match = sorted(pytz.common_timezones, key=lambda x: fuzz.ratio(timezone, x.lower()), reverse=True)[0]
|
|
return await ctx.send(f"Invalid Timezone, did you mean `{likely_match}`?")
|
|
time = datetime.datetime.now(tz).strftime("%I:%M %p") # Convert to 12-hour format
|
|
await ctx.send(f"Timezone set to **{timezone}** (`{time}`)")
|
|
await self.config.guild(ctx.guild).timezone.set(timezone)
|
|
|
|
@economytrack.command()
|
|
@commands.bot_has_permissions(embed_links=True)
|
|
async def view(self, ctx: commands.Context):
|
|
"""View EconomyTrack Settings"""
|
|
max_points = await self.config.max_points()
|
|
is_global = await bank.is_global()
|
|
conf = await self.config.guild(ctx.guild).all()
|
|
timezone = conf["timezone"]
|
|
enabled = conf["enabled"]
|
|
if is_global:
|
|
data = await self.config.data()
|
|
points = len(data)
|
|
else:
|
|
data = await self.config.guild(ctx.guild).data()
|
|
points = len(data)
|
|
avg_iter = self.looptime if self.looptime else "(N/A)"
|
|
ptime = humanize_timedelta(seconds=int(points * 60))
|
|
mptime = humanize_timedelta(seconds=int(max_points * 60))
|
|
desc = (
|
|
f"`Enabled: `{enabled}\n"
|
|
f"`Timezone: `{timezone}\n"
|
|
f"`Max Points: `{humanize_number(max_points)} ({mptime})\n"
|
|
f"`Collected: `{humanize_number(points)} ({ptime if ptime else 'None'})\n"
|
|
f"`LoopTime: `{avg_iter}ms"
|
|
)
|
|
embed = discord.Embed(title="EconomyTrack Settings", description=desc, color=ctx.author.color)
|
|
memtime = humanize_timedelta(seconds=len(conf["member_data"]) * 60)
|
|
embed.add_field(
|
|
name="Member Tracking",
|
|
value=(
|
|
f"`Enabled: `{conf['member_tracking']}\n"
|
|
f"`Collected: `{humanize_number(len(conf['member_data']))} ({memtime if memtime else 'None'})"
|
|
),
|
|
inline=False,
|
|
)
|
|
await ctx.send(embed=embed)
|
|
|
|
@commands.command()
|
|
@commands.guildowner()
|
|
@commands.guild_only()
|
|
@commands.bot_has_permissions(embed_links=True)
|
|
async def remoutliers(self, ctx: commands.Context, max_value: int, datatype: str = "bank"):
|
|
"""
|
|
Cleanup data above a certain total economy balance
|
|
|
|
**Arguments**
|
|
datatype: either `bank` or `member`
|
|
"""
|
|
if datatype.lower() in ["b", "bank", "bnk"]:
|
|
banktype = True
|
|
else:
|
|
banktype = False
|
|
|
|
is_global = await bank.is_global()
|
|
|
|
if banktype:
|
|
if is_global:
|
|
data = await self.config.data()
|
|
else:
|
|
data = await self.config.guild(ctx.guild).data()
|
|
else:
|
|
data = await self.config.guild(ctx.guild).member_data()
|
|
|
|
if len(data) < 10:
|
|
embed = discord.Embed(
|
|
description="There is not enough data collected. Try again later.",
|
|
color=discord.Color.red(),
|
|
)
|
|
return await ctx.send(embed=embed)
|
|
|
|
newrows = [i for i in data if i[1] and i[1] <= max_value]
|
|
deleted = len(data) - len(newrows)
|
|
if not deleted:
|
|
return await ctx.send("No data to delete")
|
|
|
|
async with ctx.typing():
|
|
if banktype:
|
|
if is_global:
|
|
await self.config.data.set(newrows)
|
|
else:
|
|
await self.config.guild(ctx.guild).data.set(newrows)
|
|
else:
|
|
await self.config.guild(ctx.guild).member_data.set(newrows)
|
|
await ctx.send("Deleted all data points above " + str(max_value))
|
|
|
|
@commands.command(aliases=["bgraph"])
|
|
@commands.cooldown(5, 60.0, BucketType.user)
|
|
@commands.guild_only()
|
|
@commands.bot_has_permissions(embed_links=True, attach_files=True)
|
|
async def bankgraph(self, ctx: commands.Context, timespan: str = "1d"):
|
|
"""
|
|
View bank status over a period of time.
|
|
**Arguments**
|
|
`<timespan>` How long to look for, or `all` for all-time data. Defaults to 1 day.
|
|
Must be at least 1 hour.
|
|
**Examples:**
|
|
- `[p]bankgraph 3w2d`
|
|
- `[p]bankgraph 5d`
|
|
- `[p]bankgraph all`
|
|
"""
|
|
if timespan.lower() == "all":
|
|
delta = datetime.timedelta(days=36500)
|
|
else:
|
|
delta = parse_timedelta(timespan, minimum=datetime.timedelta(hours=1))
|
|
if delta is None:
|
|
delta = datetime.timedelta(hours=1)
|
|
is_global = await bank.is_global()
|
|
currency_name = await bank.get_currency_name(ctx.guild)
|
|
bank_name = await bank.get_bank_name(ctx.guild)
|
|
if is_global:
|
|
data = await self.config.data()
|
|
else:
|
|
data = await self.config.guild(ctx.guild).data()
|
|
if len(data) < 10:
|
|
embed = discord.Embed(
|
|
description="There is not enough data collected to generate a graph right now. Try again later.",
|
|
color=discord.Color.red(),
|
|
)
|
|
return await ctx.send(embed=embed)
|
|
timezone = await self.config.guild(ctx.guild).timezone()
|
|
now = datetime.datetime.now().astimezone(tz=pytz.timezone(timezone))
|
|
start = now - delta
|
|
columns = ["ts", "total"]
|
|
rows = [i for i in data]
|
|
for i in rows:
|
|
i[0] = datetime.datetime.fromtimestamp(i[0]).astimezone(tz=pytz.timezone(timezone))
|
|
df = pd.DataFrame(rows, columns=columns)
|
|
df = df.set_index(["ts"])
|
|
df = df[~df.index.duplicated(keep="first")] # Remove duplicate indexes
|
|
mask = (df.index > start) & (df.index <= now)
|
|
df = df.loc[mask]
|
|
df = pd.DataFrame(df)
|
|
|
|
if df.empty or len(df.values) < 10: # In case there is data but it is old
|
|
embed = discord.Embed(
|
|
description="There is not enough data collected to generate a graph right now. Try again later.",
|
|
color=discord.Color.red(),
|
|
)
|
|
return await ctx.send(embed=embed)
|
|
|
|
delta: datetime.timedelta = df.index[-1] - df.index[0]
|
|
if timespan.lower() == "all":
|
|
title = f"Total economy balance for all time ({humanize_timedelta(timedelta=delta)})"
|
|
else:
|
|
title = f"Total economy balance over the last {humanize_timedelta(timedelta=delta)}"
|
|
|
|
lowest = df.min().total
|
|
highest = df.max().total
|
|
avg = df.mean().total
|
|
current = df.values[-1][0]
|
|
|
|
desc = (
|
|
f"`DataPoints: `{humanize_number(len(df.values))}\n"
|
|
f"`BankName: `{bank_name}\n"
|
|
f"`Currency: `{currency_name}"
|
|
)
|
|
|
|
field = (
|
|
f"`Current: `{humanize_number(current)}\n"
|
|
f"`Average: `{humanize_number(round(avg))}\n"
|
|
f"`Highest: `{humanize_number(highest)}\n"
|
|
f"`Lowest: `{humanize_number(lowest)}\n"
|
|
f"`Diff: `{humanize_number(highest - lowest)}"
|
|
)
|
|
|
|
first = df.values[0][0]
|
|
diff = "+" if current > first else "-"
|
|
field2 = f"{diff} {humanize_number(abs(current - first))}"
|
|
|
|
embed = discord.Embed(title=title, description=desc, color=ctx.author.color)
|
|
embed.add_field(name="Statistics", value=field)
|
|
embed.add_field(
|
|
name="Change",
|
|
value=f"Since <t:{int(df.index[0].timestamp())}:D>\n{box(field2, 'diff')}",
|
|
)
|
|
|
|
embed.set_image(url="attachment://plot.png")
|
|
embed.set_footer(text=f"Timezone: {timezone}")
|
|
async with ctx.typing():
|
|
file = await self.get_plot(df, "Total Economy Credits")
|
|
await ctx.send(embed=embed, file=file)
|
|
|
|
@commands.command(aliases=["memgraph"])
|
|
@commands.cooldown(5, 60.0, BucketType.user)
|
|
@commands.guild_only()
|
|
@commands.bot_has_permissions(embed_links=True, attach_files=True)
|
|
async def membergraph(self, ctx: commands.Context, timespan: str = "1d"):
|
|
"""
|
|
View member count over a period of time.
|
|
**Arguments**
|
|
`<timespan>` How long to look for, or `all` for all-time data. Defaults to 1 day.
|
|
Must be at least 1 hour.
|
|
**Examples:**
|
|
- `[p]membergraph 3w2d`
|
|
- `[p]membergraph 5d`
|
|
- `[p]membergraph all`
|
|
"""
|
|
if timespan.lower() == "all":
|
|
delta = datetime.timedelta(days=36500)
|
|
else:
|
|
delta = parse_timedelta(timespan, minimum=datetime.timedelta(hours=1))
|
|
if delta is None:
|
|
delta = datetime.timedelta(hours=1)
|
|
|
|
data = await self.config.guild(ctx.guild).member_data()
|
|
if len(data) < 10:
|
|
embed = discord.Embed(
|
|
description="There is not enough data collected to generate a graph right now. Try again later.",
|
|
color=discord.Color.red(),
|
|
)
|
|
return await ctx.send(embed=embed)
|
|
timezone = await self.config.guild(ctx.guild).timezone()
|
|
now = datetime.datetime.now().astimezone(tz=pytz.timezone(timezone))
|
|
start = now - delta
|
|
columns = ["ts", "total"]
|
|
rows = [i for i in data]
|
|
for i in rows:
|
|
i[0] = datetime.datetime.fromtimestamp(i[0]).astimezone(tz=pytz.timezone(timezone))
|
|
df = pd.DataFrame(rows, columns=columns)
|
|
df = df.set_index(["ts"])
|
|
df = df[~df.index.duplicated(keep="first")] # Remove duplicate indexes
|
|
mask = (df.index > start) & (df.index <= now)
|
|
df = df.loc[mask]
|
|
df = pd.DataFrame(df)
|
|
|
|
if df.empty or len(df.values) < 10: # In case there is data but it is old
|
|
embed = discord.Embed(
|
|
description="There is not enough data collected to generate a graph right now. Try again later.",
|
|
color=discord.Color.red(),
|
|
)
|
|
return await ctx.send(embed=embed)
|
|
|
|
delta: datetime.timedelta = df.index[-1] - df.index[0]
|
|
if timespan.lower() == "all":
|
|
title = f"Total member count for all time ({humanize_timedelta(timedelta=delta)})"
|
|
else:
|
|
title = f"Total member count over the last {humanize_timedelta(timedelta=delta)}"
|
|
|
|
lowest = df.min().total
|
|
highest = df.max().total
|
|
avg = df.mean().total
|
|
current = df.values[-1][0]
|
|
|
|
desc = f"`DataPoints: `{humanize_number(len(df.values))}"
|
|
|
|
field = (
|
|
f"`Current: `{humanize_number(current)}\n"
|
|
f"`Average: `{humanize_number(round(avg))}\n"
|
|
f"`Highest: `{humanize_number(highest)}\n"
|
|
f"`Lowest: `{humanize_number(lowest)}\n"
|
|
f"`Diff: `{humanize_number(highest - lowest)}"
|
|
)
|
|
|
|
first = df.values[0][0]
|
|
diff = "+" if current > first else "-"
|
|
field2 = f"{diff} {humanize_number(abs(current - first))}"
|
|
|
|
embed = discord.Embed(title=title, description=desc, color=ctx.author.color)
|
|
embed.add_field(name="Statistics", value=field)
|
|
embed.add_field(
|
|
name="Change",
|
|
value=f"Since <t:{int(df.index[0].timestamp())}:D>\n{box(field2, 'diff')}",
|
|
)
|
|
|
|
embed.set_image(url="attachment://plot.png")
|
|
embed.set_footer(text=f"Timezone: {timezone}")
|
|
async with ctx.typing():
|
|
file = await self.get_plot(df, "Member Count")
|
|
await ctx.send(embed=embed, file=file)
|