Repository: python-discord/bot

Issue:
Voice Gate: Better `!user` command
To aid our moderators in moderating the voice channels, we'll add two new parameters to the `!user` command:
- Total messages sent on the server
- Time verified, which shows when the user verified that they accepted our rules.

This data will be made available through a new site API, which is specified here: https://github.com/python-discord/site/issues/398

**You cannot work on this issue until this site issue has been solved!**


Discussion:
If I'm wrong about any of this, please correct me.

> Total messages sent on the server

Would this require having to iterate through all message history sent by the user, or is it automatically stored somewhere that I'm not aware of?

If we did decide to do message history such as the example below, wouldn't this count as a bunch of API calls?
```python
messages = await channel.history(limit=None).flatten()
```

To avoid the possible many message history API calls maybe we should limit how many messages should be iterated through from the user (like `limit=100`).
No, Metricity already tracks this.
[This issue](https://github.com/python-discord/site/issues/398) will solve these problems.

I just noticed it was even mentioned in the original issue, I was being blind. Sorry for the inconvenience. 
I can take this issue once the API endpoints are written.
Decision here was to keep this to staff channels to prevent users trying to pad their stats and repeatedly running user. Discord provides a search tool for regular users which suffices, this is just a moderation tool.
On 27 Sep 2020, 20:15 +0100, Xithrius <notifications@github.com>, wrote:
> I can take this issue once the API endpoints are written.
> Also, should we allow users to retrieve data on how many messages they've sent, or should we only allow this in staff channels?
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub, or unsubscribe.

Thank you for the clarification.
try to code it yourself
@Xithrius looks like the API is ready
@mbaruh Thank you for the update. I will start working on this soon.

Resolving patch:
diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py
--- a/bot/exts/info/information.py
+++ b/bot/exts/info/information.py
@@ -6,11 +6,13 @@
 from string import Template
 from typing import Any, Mapping, Optional, Tuple, Union
 
+from dateutil import parser
 from discord import ChannelType, Colour, Embed, Guild, Message, Role, Status, utils
 from discord.abc import GuildChannel
 from discord.ext.commands import BucketType, Cog, Context, Paginator, command, group, has_any_role
 
 from bot import constants
+from bot.api import ResponseCodeError
 from bot.bot import Bot
 from bot.converters import FetchedMember
 from bot.decorators import in_whitelist
@@ -21,7 +23,6 @@
 
 log = logging.getLogger(__name__)
 
-
 STATUS_EMOTES = {
     Status.offline: constants.Emojis.status_offline,
     Status.dnd: constants.Emojis.status_dnd,
@@ -224,13 +225,16 @@ async def create_user_embed(self, ctx: Context, user: FetchedMember) -> Embed:
             if is_set and (emoji := getattr(constants.Emojis, f"badge_{badge}", None)):
                 badges.append(emoji)
 
+        verified_at, activity = await self.user_verification_and_messages(user)
+
         if on_server:
             joined = time_since(user.joined_at, max_units=3)
             roles = ", ".join(role.mention for role in user.roles[1:])
-            membership = textwrap.dedent(f"""
-                             Joined: {joined}
-                             Roles: {roles or None}
-                         """).strip()
+            membership = {"Joined": joined, "Verified": verified_at or "False", "Roles": roles or None}
+            if not is_mod_channel(ctx.channel):
+                membership.pop("Verified")
+
+            membership = textwrap.dedent("\n".join([f"{key}: {value}" for key, value in membership.items()]))
         else:
             roles = None
             membership = "The user is not a member of the server"
@@ -252,6 +256,8 @@ async def create_user_embed(self, ctx: Context, user: FetchedMember) -> Embed:
 
         # Show more verbose output in moderation channels for infractions and nominations
         if is_mod_channel(ctx.channel):
+            fields.append(activity)
+
             fields.append(await self.expanded_user_infraction_counts(user))
             fields.append(await self.user_nomination_counts(user))
         else:
@@ -354,6 +360,39 @@ async def user_nomination_counts(self, user: FetchedMember) -> Tuple[str, str]:
 
         return "Nominations", "\n".join(output)
 
+    async def user_verification_and_messages(self, user: FetchedMember) -> Tuple[Union[bool, str], Tuple[str, str]]:
+        """
+        Gets the time of verification and amount of messages for `member`.
+
+        Fetches information from the metricity database that's hosted by the site.
+        If the database returns a code besides a 404, then many parts of the bot are broken including this one.
+        """
+        activity_output = []
+        verified_at = False
+
+        try:
+            user_activity = await self.bot.api_client.get(f"bot/users/{user.id}/metricity_data")
+        except ResponseCodeError as e:
+            if e.status == 404:
+                activity_output = "No activity"
+
+        else:
+            try:
+                if (verified_at := user_activity["verified_at"]) is not None:
+                    verified_at = time_since(parser.isoparse(verified_at), max_units=3)
+            except ValueError:
+                log.warning(f"Could not parse ISO string correctly for user {user.id} verification date.")
+                verified_at = None
+
+            activity_output.append(user_activity["total_messages"] or "No messages")
+            activity_output.append(user_activity["activity_blocks"] or "No activity")
+
+            activity_output = "\n".join(
+                f"{name}: {metric}" for name, metric in zip(["Messages", "Activity blocks"], activity_output)
+            )
+
+        return verified_at, ("Activity", activity_output)
+
     def format_fields(self, mapping: Mapping[str, Any], field_width: Optional[int] = None) -> str:
         """Format a mapping to be readable to a human."""
         # sorting is technically superfluous but nice if you want to look for a specific field


Test patch:
diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py
--- a/tests/bot/exts/info/test_information.py
+++ b/tests/bot/exts/info/test_information.py
@@ -355,6 +355,7 @@ async def test_create_user_embed_expanded_information_in_moderation_channels(
         self.assertEqual(
             textwrap.dedent(f"""
                 Joined: {"1 year ago"}
+                Verified: {"False"}
                 Roles: &Moderators
             """).strip(),
             embed.fields[1].value