Skip to content

Commit

Permalink
Add activity command
Browse files Browse the repository at this point in the history
  • Loading branch information
WitherredAway committed Oct 30, 2024
1 parent acca78e commit 768ab7f
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 1 deletion.
2 changes: 2 additions & 0 deletions cogs/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ async def on_command_error(self, ctx, error):
f"You're on cooldown! Try again in **{time.human_timedelta(timedelta(seconds=error.retry_after))}**.",
ephemeral=True,
)
if isinstance(error, commands.CommandInvokeError) and "private_variable" in str(error.original):
await ctx.send(error.original, ephemeral=True)
elif isinstance(error, commands.BadFlagArgument):
if isinstance(error.original, commands.ConversionError):
return await ctx.send(error.original.original, ephemeral=True)
Expand Down
20 changes: 20 additions & 0 deletions cogs/mongo.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
from dataclasses import MISSING
from datetime import timezone
from typing import Any, Optional

import discord
from bson.codec_options import CodecOptions
from discord.ext import commands
from motor.motor_asyncio import AsyncIOMotorClient


class PrivateVariableNotFound(Exception):
pass


class Mongo(commands.Cog):
"""For database operations."""

Expand All @@ -18,6 +24,9 @@ def __init__(self, bot):
self.poketwo_client = AsyncIOMotorClient(bot.config.POKETWO_DATABASE_URI, io_loop=bot.loop)
self.poketwo_db = self.poketwo_client[bot.config.POKETWO_DATABASE_NAME]

async def cog_load(self):
await self.db.private_variable.create_index([("name", 1)], unique=True)

async def reserve_id(self, name, reserve=1):
result = await self.db.counter.find_one_and_update({"_id": name}, {"$inc": {"next": reserve}}, upsert=True)
if result is None:
Expand All @@ -33,6 +42,17 @@ async def fetch_next_idx(self, member: discord.Member, reserve=1):
await self.bot.poketwo_redis.hdel(f"db:member", member.id)
return result["next_idx"]

async def fetch_private_variable(self, name: str, default: Optional[Any] = MISSING) -> Any:
doc = await self.db.private_variable.find_one({"name": name})
if not doc:
if default is MISSING:
raise PrivateVariableNotFound(
f"No private variable with name '{name}' found, please ask a Developer to set it in the 'private_variable' collection first."
)
else:
return default
return doc["value"]


async def setup(bot):
await bot.add_cog(Mongo(bot))
120 changes: 119 additions & 1 deletion cogs/poketwo_administration.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@
import random
from dataclasses import dataclass
from datetime import datetime, timezone
from textwrap import dedent
from typing import Any, Dict, List, Literal, Optional

import discord
from bson.objectid import ObjectId
from discord.ext import commands

from cogs.mongo import PrivateVariableNotFound
from data.models import Species
from helpers import checks, constants
from helpers.converters import SpeciesConverter
from helpers.outline.converters import ActivityDateArgs, MonthConverter
from helpers.poketwo import format_pokemon_details
from helpers.utils import FetchUserConverter

Expand All @@ -32,6 +35,19 @@ def update(d, u):
return d


def tabulate(data: List[List[str]]):
"""Function to tabulate data. The data"""

ncols = len(data[0])
header_lens = [max([len(str(d[i])) for d in data]) for i in range(ncols)]

row_format = "| " + " | ".join(["{:<%s}" % header_lens[i] for i in range(ncols)]) + " |"
border = "+-" + "-+-".join([f"{'':-<{hl}}" for hl in header_lens]) + "-+"

table = [border, row_format.format(*data[0]), border, *[row_format.format(*row) for row in data[1:]], border]
return "\n".join(table)


class PokemonRefundFlags(commands.FlagConverter, case_insensitive=True):
species: Species = commands.flag(converter=SpeciesConverter)
nature: Optional[str] = commands.flag(default=random_nature)
Expand Down Expand Up @@ -399,8 +415,8 @@ async def manager(self, ctx):

await ctx.send_help(ctx.command)

@commands.check_any(checks.is_server_manager(), checks.is_bot_manager())
@manager.command(aliases=("givecoins", "ac", "gc"))
@commands.check_any(checks.is_server_manager(), checks.is_bot_manager())
async def addcoins(self, ctx, user: FetchUserConverter, amt: int, *, notes: Optional[str] = None):
"""Add to a user's balance."""

Expand All @@ -416,6 +432,108 @@ async def addcoins(self, ctx, user: FetchUserConverter, amt: int, *, notes: Opti
embed=self.logs_embed(ctx.author, user, "Gave pokécoins to", f"**Pokécoins:** {amt}", notes), view=view
)

@manager.command()
@checks.is_server_manager()
@checks.staff_categories_only()
async def activity(
self,
ctx,
role_or_user: Optional[discord.Role | discord.Member | discord.User] = None,
*,
date: ActivityDateArgs,
):
"""Get ticket and bot-logs activity"""

role_or_user = role_or_user or next(
(r for r_id in constants.MODERATOR_ROLES[-2:] if (r := ctx.guild.get_role(r_id))), None
)
if not role_or_user:
return await ctx.send("Role/user not found.")

if isinstance(role_or_user, discord.Role):
members = role_or_user.members
elif isinstance(role_or_user, (discord.Member, discord.User)):
members = [role_or_user]

now = discord.utils.utcnow()

from_month = date.month
to_month = now.month

from_year = date.year if date.year is not None else now.year
to_year = from_year

if from_month:
# If user passed in a month value, should check logs from that month
to_month = from_month + 1
if to_month > 12:
# Incase until_month exceeds 12, increase year and loop it forward
to_year += 1
to_month = to_month - from_month
else:
# If user passed in a month value, should check logs from previous month
from_month = now.month - 1
if from_month < 1:
# Incase month goes below 1, decrease year and loop it backwards
from_year -= 1
from_month = 12

from_dt = now.replace(year=from_year, month=from_month, day=1, hour=0, minute=0, second=0)
to_dt = from_dt.replace(year=to_year, month=to_month)
_filter = {"$gte": from_dt, "$lt": to_dt}

cols = await self.bot.mongo.fetch_private_variable("activity_columns")
bnet = await self.bot.mongo.fetch_private_variable("activity_bot_logs_net")
tnet = await self.bot.mongo.fetch_private_variable("activity_tickets_net")
max_amount = await self.bot.mongo.fetch_private_variable("activity_max_amount")
min_total = await self.bot.mongo.fetch_private_variable("activity_min_total")

net = lambda b, t: b * bnet + t * tnet
data = []
for member in members:
tickets = await self.bot.mongo.db.ticket.count_documents({"agent_id": member.id, "closed_at": _filter})
bot_logs = await self.bot.mongo.db.action.count_documents({"user_id": member.id, "created_at": _filter})
total = net(bot_logs, tickets)

raw = round(total * 100)
amount = min(max_amount, raw if total >= min_total else 0)

data.append([member.name, bot_logs, tickets, total, raw, amount])

data.sort(key=lambda t: t[4], reverse=True)

if len(members) > 1:
data.append(["" for _ in cols])
data.append(
["TOTAL", *[sum([d[i] if not isinstance(d[i], str) else 0 for d in data]) for i in range(1, len(cols))]]
)

table = tabulate([cols, *data])
msgs = [
dedent(
f"""
### Number of Bot Logs And Tickets By {role_or_user.mention}
No. of actions in #bot-logs and tickets (both SS and OS, *latest agent only*) by each {role_or_user.name} in {from_dt:%B}
> **From**: {discord.utils.format_dt(from_dt)}
> **To**: {discord.utils.format_dt(to_dt)}
> **Min Cut-off**: {min_total}
> **Formula**: `(bot-logs * {bnet} + tickets * {tnet}) * 100`
> **Max Amount**: {max_amount}
"""
),
f"""{"`"*3}py\n{table}\n{"`"*3}""",
]

if len("".join(msgs)) < constants.CONTENT_CHAR_LIMIT:
msgs = ["".join(msgs)]

og = ctx
for msg in msgs:
og = await og.reply(
msg,
allowed_mentions=discord.AllowedMentions.none(),
)


async def setup(bot):
await bot.add_cog(PoketwoAdministration(bot))
1 change: 1 addition & 0 deletions helpers/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
SUPPORT_SERVER_ID = 930339868503048202

EMBED_FIELD_CHAR_LIMIT = 1024
CONTENT_CHAR_LIMIT = 2000

POKEMON_NATURES = [
"Adamant",
Expand Down
48 changes: 48 additions & 0 deletions helpers/converters.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import contextlib
from datetime import datetime
from typing import Optional
import discord
from discord.ext import commands

from helpers.context import GuiduckContext


class SpeciesConverter(commands.Converter):
async def convert(self, ctx, arg):
Expand Down Expand Up @@ -29,3 +34,46 @@ async def convert(self, ctx, arg):
return await ctx.bot.fetch_channel(int(arg))
except (discord.NotFound, discord.HTTPException, ValueError):
raise commands.ChannelNotFound(arg)


class MonthConverter(commands.Converter):
async def convert(self, ctx: GuiduckContext, argument: Optional[str] = None) -> int:
now = discord.utils.utcnow()
if not argument:
return now.month

if argument.isdigit():
result = int(argument)
if result < 1 or result > 12:
raise ValueError("Month number can be 1-12")

return result

dt = None
for spec in ["%B", "%b"]:
with contextlib.suppress(ValueError):
dt = datetime.strptime(argument, spec)
if dt:
break

if not dt:
raise ValueError("Invalid month provided")

return dt.month


class ActivityDateArgs(commands.FlagConverter):
"""Date flags for activity command"""

month: MonthConverter = commands.flag(
aliases=("m", "mo"),
description="The month",
max_args=1,
default=None,
)
year: int = commands.flag(
aliases=("y",),
description="The year",
max_args=1,
default=None,
)

0 comments on commit 768ab7f

Please sign in to comment.