Repository: hikari-py/hikari
Pull request #1659 (2023-07-15 09:17:38)

Add with_counts information for OwnGuilds


Patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 71cf5088..7d84b6f0 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,7 +32,7 @@ jobs:
           python-version: 3.8
 
       - name: Temporarily disable "include administrators" branch protection
-        uses: benjefferies/branch-protection-bot@1.0.8
+        uses: benjefferies/branch-protection-bot@1.0.9
         with:
           access_token: ${{ steps.generate_token.outputs.token }}
           enforce_admins: false
@@ -46,7 +46,7 @@ jobs:
         run: bash scripts/ci/release.sh
 
       - name: Re-enable "include administrators" branch protection
-        uses: benjefferies/branch-protection-bot@1.0.8
+        uses: benjefferies/branch-protection-bot@1.0.9
         if: always()
         with:
           access_token: ${{ steps.generate_token.outputs.token }}
diff --git a/changes/1659.feature.md b/changes/1659.feature.md
new file mode 100644
index 00000000..11b107d0
--- /dev/null
+++ b/changes/1659.feature.md
@@ -0,0 +1,1 @@
+Add `approximate_member_count` and `approximate_presence_count` to `OwnGuild`.
diff --git a/changes/1673.bugfix.md b/changes/1673.bugfix.md
new file mode 100644
index 00000000..e0d8fdcc
--- /dev/null
+++ b/changes/1673.bugfix.md
@@ -0,0 +1,1 @@
+Fix incorrect calculation for the default avatar of migrated users
diff --git a/dev-requirements/build.txt b/dev-requirements/build.txt
index 34df0cf4..f8138410 100644
--- a/dev-requirements/build.txt
+++ b/dev-requirements/build.txt
@@ -1,2 +1,2 @@
 setuptools>=65.2.0
-wheel==0.40.0
+wheel==0.41.1
diff --git a/dev-requirements/flake8.txt b/dev-requirements/flake8.txt
index 7fca0735..d9a8845d 100644
--- a/dev-requirements/flake8.txt
+++ b/dev-requirements/flake8.txt
@@ -1,4 +1,4 @@
-flake8==6.0.0
+flake8==6.1.0
 
 # Plugins
 # Ref: https://github.com/DmytroLitvinov/awesome-flake8-extensions
diff --git a/dev-requirements/pyright.txt b/dev-requirements/pyright.txt
index 9bbcd6a1..f42b8976 100644
--- a/dev-requirements/pyright.txt
+++ b/dev-requirements/pyright.txt
@@ -1,1 +1,1 @@
-pyright==1.1.317
+pyright==1.1.320
diff --git a/dev-requirements/slotscheck.txt b/dev-requirements/slotscheck.txt
index effa63a5..ea1c50f9 100644
--- a/dev-requirements/slotscheck.txt
+++ b/dev-requirements/slotscheck.txt
@@ -1,1 +1,1 @@
-slotscheck==0.16.5
+slotscheck==0.17.0
diff --git a/dev-requirements/sphinx.txt b/dev-requirements/sphinx.txt
index c91cff01..4f33125f 100644
--- a/dev-requirements/sphinx.txt
+++ b/dev-requirements/sphinx.txt
@@ -1,7 +1,7 @@
-sphinx==7.0.1
+sphinx==7.1.2
 
 # Theme
-furo==2023.5.20
+furo==2023.7.26
 
 # Autodocumentation
 sphinx-autoapi==2.1.1
diff --git a/hikari/applications.py b/hikari/applications.py
index c60ab6ee..e6472e3f 100644
--- a/hikari/applications.py
+++ b/hikari/applications.py
@@ -308,6 +308,12 @@ class OwnGuild(guilds.PartialGuild):
     my_permissions: permissions_.Permissions = attrs.field(eq=False, hash=False, repr=False)
     """The guild-level permissions that apply to the current user or bot."""
 
+    approximate_member_count: int = attrs.field(eq=False, hash=False, repr=True)
+    """The approximate amount of members in this guild."""
+
+    approximate_active_member_count: int = attrs.field(eq=False, hash=False, repr=True)
+    """The approximate amount of presences in this guild."""
+
 
 @attrs.define(hash=True, kw_only=True, weakref_slot=False)
 class OwnApplicationRoleConnection:
diff --git a/hikari/embeds.py b/hikari/embeds.py
index a6f27ec7..6a2eeb16 100644
--- a/hikari/embeds.py
+++ b/hikari/embeds.py
@@ -427,6 +427,8 @@ def timestamp(self) -> typing.Optional[datetime.datetime]:
             To generate a timezone aware timestamp, use one of the following
             snippets:
 
+            .. code-block:: python
+
                 # Use UTC.
                 >>> datetime.datetime.now(tz=datetime.timezone.utc)
                 datetime.datetime(2020, 6, 5, 18, 29, 56, 424744, tzinfo=datetime.timezone.utc)
@@ -440,7 +442,9 @@ def timestamp(self) -> typing.Optional[datetime.datetime]:
 
             You can generate a timezone-aware timestamp instead of a timezone-naive
             one by specifying a timezone. Hikari will detect any difference in
-            timezone if the timestamp is non timezone-naive and fix it for you.
+            timezone if the timestamp is non timezone-naive and fix it for you:
+
+            .. code-block:: python
 
                 # I am British, and it is June, so we are in daylight saving
                 # (UTC+1 or GMT+1, specifically).
@@ -463,7 +467,9 @@ def timestamp(self) -> typing.Optional[datetime.datetime]:
                 >>> ...
 
             A library on PyPI called [tzlocal](...) also exists that may be useful
-            to you if you need to get your local timezone for any reason.
+            to you if you need to get your local timezone for any reason:
+
+            .. code-block:: python
 
                 >>> import datetime
                 >>> import tzlocal
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index d6369063..aaa8fbed 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -589,6 +589,8 @@ def deserialize_own_guild(self, payload: data_binding.JSONObject) -> application
             features=[guild_models.GuildFeature(feature) for feature in payload["features"]],
             is_owner=bool(payload["owner"]),
             my_permissions=permission_models.Permissions(int(payload["permissions"])),
+            approximate_member_count=int(payload["approximate_member_count"]),
+            approximate_active_member_count=int(payload["approximate_presence_count"]),
         )
 
     def deserialize_own_application_role_connection(
@@ -1169,18 +1171,21 @@ def deserialize_guild_stage_channel(
         guild_id: undefined.UndefinedOr[snowflakes.Snowflake] = undefined.UNDEFINED,
     ) -> channel_models.GuildStageChannel:
         channel_fields = self._set_guild_channel_attrsibutes(payload, guild_id=guild_id)
-        permission_overwrites = {
-            snowflakes.Snowflake(overwrite["id"]): self.deserialize_permission_overwrite(overwrite)
-            for overwrite in payload["permission_overwrites"]
-        }
-        last_message_id = snowflakes.Snowflake(payload["last_message_id"]) if "last_message_id" in payload else None
+
+        last_message_id: typing.Optional[snowflakes.Snowflake] = None
+        if (raw_last_message_id := payload.get("last_message_id")) is not None:
+            last_message_id = snowflakes.Snowflake(raw_last_message_id)
+
         return channel_models.GuildStageChannel(
             app=self._app,
             id=channel_fields.id,
             name=channel_fields.name,
             type=channel_fields.type,
             guild_id=channel_fields.guild_id,
-            permission_overwrites=permission_overwrites,
+            permission_overwrites={
+                snowflakes.Snowflake(overwrite["id"]): self.deserialize_permission_overwrite(overwrite)
+                for overwrite in payload["permission_overwrites"]
+            },
             is_nsfw=payload.get("nsfw", False),
             parent_id=channel_fields.parent_id,
             region=payload["rtc_region"],
diff --git a/hikari/impl/special_endpoints.py b/hikari/impl/special_endpoints.py
index f760d735..c2a3eb99 100644
--- a/hikari/impl/special_endpoints.py
+++ b/hikari/impl/special_endpoints.py
@@ -598,6 +598,7 @@ def __init__(
 
     async def _next_chunk(self) -> typing.Optional[typing.Generator[applications.OwnGuild, typing.Any, None]]:
         query = data_binding.StringMapBuilder()
+        query.put("with_counts", True)
         query.put("before" if self._newest_first else "after", self._first_id)
         # We rely on Discord's default for the limit here since for this endpoint this has always scaled
         # along side the maximum page size limit to match the maximum amount of guilds a user can be in.
diff --git a/hikari/internal/routes.py b/hikari/internal/routes.py
index 75b9a634..0db58bbf 100644
--- a/hikari/internal/routes.py
+++ b/hikari/internal/routes.py
@@ -571,7 +571,7 @@ def compile_to_file(
 )
 CDN_GUILD_BANNER: typing.Final[CDNRoute] = CDNRoute("/banners/{guild_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF})
 
-CDN_DEFAULT_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/embed/avatars/{discriminator}", {PNG}, is_sizable=False)
+CDN_DEFAULT_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/embed/avatars/{style}", {PNG}, is_sizable=False)
 CDN_USER_AVATAR: typing.Final[CDNRoute] = CDNRoute("/avatars/{user_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF})
 CDN_USER_BANNER: typing.Final[CDNRoute] = CDNRoute("/banners/{user_id}/{hash}", {PNG, *JPEG_JPG, WEBP, GIF})
 CDN_MEMBER_AVATAR: typing.Final[CDNRoute] = CDNRoute(
diff --git a/hikari/users.py b/hikari/users.py
index a993f30f..b58e1562 100644
--- a/hikari/users.py
+++ b/hikari/users.py
@@ -497,11 +497,11 @@ def default_avatar_url(self) -> files.URL:
         """Default avatar URL for this user."""
         if self.discriminator == "0":  # migrated account
             return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file(
-                urls.CDN_URL, discriminator=self.id.created_at.timestamp() % 6, file_format="png"
+                urls.CDN_URL, style=(self.id >> 22) % 6, file_format="png"
             )
 
         return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file(
-            urls.CDN_URL, discriminator=int(self.discriminator) % 5, file_format="png"
+            urls.CDN_URL, style=int(self.discriminator) % 5, file_format="png"
         )
 
     @property
diff --git a/hikari/webhooks.py b/hikari/webhooks.py
index 1080a4a5..261e0191 100644
--- a/hikari/webhooks.py
+++ b/hikari/webhooks.py
@@ -526,7 +526,7 @@ def avatar_url(self) -> typing.Optional[files_.URL]:
     @property
     def default_avatar_url(self) -> files_.URL:
         """Default avatar URL for the user."""
-        return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file(urls.CDN_URL, discriminator=0, file_format="png")
+        return routes.CDN_DEFAULT_USER_AVATAR.compile_to_file(urls.CDN_URL, style=0, file_format="png")
 
     def make_avatar_url(self, ext: str = "png", size: int = 4096) -> typing.Optional[files_.URL]:
         """Generate the avatar URL for this webhook's custom avatar if set.


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 9ad10d49..c7a2f34a 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -895,16 +895,21 @@ def own_guild_payload(self):
             "owner": False,
             "permissions": "2147483647",
             "features": ["DISCOVERABLE", "FORCE_RELAY"],
+            "approximate_member_count": 3268,
+            "approximate_presence_count": 784,
         }
 
     def test_deserialize_own_guild(self, entity_factory_impl, mock_app, own_guild_payload):
         own_guild = entity_factory_impl.deserialize_own_guild(own_guild_payload)
+
         assert own_guild.id == 152559372126519269
         assert own_guild.name == "Isopropyl"
         assert own_guild.icon_hash == "d4a983885dsaa7691ce8bcaaf945a"
         assert own_guild.features == [guild_models.GuildFeature.DISCOVERABLE, "FORCE_RELAY"]
         assert own_guild.is_owner is False
         assert own_guild.my_permissions == permission_models.Permissions(2147483647)
+        assert own_guild.approximate_member_count == 3268
+        assert own_guild.approximate_active_member_count == 784
 
     def test_deserialize_own_guild_with_null_and_unset_fields(self, entity_factory_impl):
         own_guild = entity_factory_impl.deserialize_own_guild(
@@ -915,6 +920,8 @@ def test_deserialize_own_guild_with_null_and_unset_fields(self, entity_factory_i
                 "owner": False,
                 "permissions": "2147483647",
                 "features": ["DISCOVERABLE", "FORCE_RELAY"],
+                "approximate_member_count": 3268,
+                "approximate_presence_count": 784,
             }
         )
         assert own_guild.icon_hash is None
@@ -2023,7 +2030,7 @@ def test_deserialize_guild_stage_channel(
         assert isinstance(voice_channel, channel_models.GuildStageChannel)
 
     def test_deserialize_guild_stage_channel_with_null_fields(self, entity_factory_impl):
-        voice_channel = entity_factory_impl.deserialize_guild_voice_channel(
+        voice_channel = entity_factory_impl.deserialize_guild_stage_channel(
             {
                 "id": "123",
                 "permission_overwrites": [],
diff --git a/tests/hikari/impl/test_special_endpoints.py b/tests/hikari/impl/test_special_endpoints.py
index 4eefd3bb..836eb1fb 100644
--- a/tests/hikari/impl/test_special_endpoints.py
+++ b/tests/hikari/impl/test_special_endpoints.py
@@ -100,9 +100,9 @@ async def test_aiter(self):
         )
         mock_request.assert_has_awaits(
             [
-                mock.call(compiled_route=expected_route, query={"after": "123321"}),
-                mock.call(compiled_route=expected_route, query={"after": "123321124123"}),
-                mock.call(compiled_route=expected_route, query={"after": "12332112432234"}),
+                mock.call(compiled_route=expected_route, query={"after": "123321", "with_counts": "true"}),
+                mock.call(compiled_route=expected_route, query={"after": "123321124123", "with_counts": "true"}),
+                mock.call(compiled_route=expected_route, query={"after": "12332112432234", "with_counts": "true"}),
             ]
         )
 
@@ -148,9 +148,9 @@ async def test_aiter_when_newest_first(self):
         )
         mock_request.assert_has_awaits(
             [
-                mock.call(compiled_route=expected_route, query={"before": "55555555555555555"}),
-                mock.call(compiled_route=expected_route, query={"before": "1213321124123"}),
-                mock.call(compiled_route=expected_route, query={"before": "1213321123123"}),
+                mock.call(compiled_route=expected_route, query={"before": "55555555555555555", "with_counts": "true"}),
+                mock.call(compiled_route=expected_route, query={"before": "1213321124123", "with_counts": "true"}),
+                mock.call(compiled_route=expected_route, query={"before": "1213321123123", "with_counts": "true"}),
             ]
         )
 
@@ -168,7 +168,8 @@ async def test_aiter_when_empty_chunk(self, newest_first: bool):
 
         assert result == []
         mock_entity_factory.deserialize_own_guild.assert_not_called()
-        query = {"before" if newest_first else "after": "123321"}
+        order_key = "before" if newest_first else "after"
+        query = {order_key: "123321", "with_counts": "true"}
         mock_request.assert_awaited_once_with(compiled_route=expected_route, query=query)
 
 
diff --git a/tests/hikari/internal/test_enums.py b/tests/hikari/internal/test_enums.py
index 46150cb5..05433791 100644
--- a/tests/hikari/internal/test_enums.py
+++ b/tests/hikari/internal/test_enums.py
@@ -202,7 +202,7 @@ class Enum(int, enums.Enum):
 
         returned = Enum(9)
         assert returned == Enum.foo
-        assert type(returned) == Enum
+        assert type(returned) is Enum
 
     def test_call_when_not_member(self):
         class Enum(int, enums.Enum):
@@ -212,7 +212,7 @@ class Enum(int, enums.Enum):
 
         returned = Enum(69)
         assert returned == 69
-        assert type(returned) != Enum
+        assert type(returned) is not Enum
 
     def test_getitem(self):
         class Enum(int, enums.Enum):
@@ -222,7 +222,7 @@ class Enum(int, enums.Enum):
 
         returned = Enum["foo"]
         assert returned == Enum.foo
-        assert type(returned) == Enum
+        assert type(returned) is Enum
 
     def test_contains(self):
         class Enum(int, enums.Enum):
@@ -1252,7 +1252,7 @@ class TestFlag(enums.Flag):
 
         returned = TestFlag["FOO"]
         assert returned == TestFlag.FOO
-        assert type(returned) == TestFlag
+        assert type(returned) is TestFlag
 
     def test_repr(self):
         class TestFlag(enums.Flag):
diff --git a/tests/hikari/test_users.py b/tests/hikari/test_users.py
index 681ca54c..3b5b2a22 100644
--- a/tests/hikari/test_users.py
+++ b/tests/hikari/test_users.py
@@ -254,7 +254,19 @@ def test_default_avatar(self, obj):
         ) as route:
             assert obj.default_avatar_url == "file"
 
-        route.compile_to_file.assert_called_once_with(urls.CDN_URL, discriminator=4, file_format="png")
+        route.compile_to_file.assert_called_once_with(urls.CDN_URL, style=4, file_format="png")
+
+    def test_default_avatar_for_migrated_users(self, obj):
+        obj.id = 377812572784820226
+        obj.avatar_hash = "18dnf8dfbakfdh"
+        obj.discriminator = "0"
+
+        with mock.patch.object(
+            routes, "CDN_DEFAULT_USER_AVATAR", new=mock.Mock(compile_to_file=mock.Mock(return_value="file"))
+        ) as route:
+            assert obj.default_avatar_url == "file"
+
+        route.compile_to_file.assert_called_once_with(urls.CDN_URL, style=0, file_format="png")
 
     def test_banner_url_property(self, obj):
         with mock.patch.object(users.User, "make_banner_url") as make_banner_url: