Repository: hikari-py/hikari (python)

Issue:
KeyError when rate limited
### Steps to reproduce

Get rate limited.

### Expected result

Get a proper error message or do some waiting logic.

### Actual result

We got rate limited and it keeps raising the KeyError "retry_after" in `hikari/impl/rest.py` function `_parse_ratelimits` line 991 `body_retry_after = float(body["retry_after"])`.
The `body` looks like this: `{'code': 0, 'message': 'You are being blocked from accessing our API temporarily due to exceeding our rate limits frequently. Please read our docs at https://discord.com/developers/docs/topics/rate-limits to prevent this moving forward.'}`

### System info

```shell
hikari (2.0.0) [b5ce62d3]
located at /home/Project/venv/lib/python3.12/site-packages/hikari
CPython 3.12.7 GCC 14.2.1 20240910
Linux mysys 6.6.65-1-MANJARO #1 SMP PREEMPT_DYNAMIC Wed Dec 11 22:24:04 UTC 2024 x86_64
```

### Further info

_No response_

### Checklist

- [x] I have made sure to remove ANY sensitive information (bot token, passwords, credentials, personal details, etc.).
- [x] I have searched the issue tracker and have made sure it's not a duplicate. If it is a follow up of another issue, I have specified it.

Pull request description:
Properly handle cloudflare bans
### Summary
<!-- Small summary of the merge request -->

### Checklist
<!-- Make sure to tick all the following boxes by putting an `x` in between (like this `[x]`) -->
- [ ] I have run `nox` and all the pipelines have passed.
- [ ] I have made unittests according to the code I have added/modified/deleted.

### Related issues
Closes #2152


Patch:
diff --git a/changes/2105.feature.md b/changes/2105.feature.md
new file mode 100644
index 00000000..81fbbf62
--- /dev/null
+++ b/changes/2105.feature.md
@@ -0,0 +1,1 @@
+Allow setting `name` and `value` of an `EmbedField` to an empty string.
diff --git a/changes/2168.bugfix.md b/changes/2168.bugfix.md
new file mode 100644
index 00000000..ffd7c510
--- /dev/null
+++ b/changes/2168.bugfix.md
@@ -0,0 +1,1 @@
+Properly handle and error on Cloudflare bans
diff --git a/hikari/embeds.py b/hikari/embeds.py
index 134b6918..aadf1dd8 100644
--- a/hikari/embeds.py
+++ b/hikari/embeds.py
@@ -788,17 +788,15 @@ def set_thumbnail(self, image: typing.Optional[files.Resourceish] = None, /) ->
 
         return self
 
-    def add_field(self, name: str, value: str, *, inline: bool = False) -> Embed:
+    def add_field(self, name: str | None = None, value: str | None = None, *, inline: bool = False) -> Embed:
         """Add a new field to this embed.
 
         Parameters
         ----------
         name
-            The mandatory non-empty field name. This must contain at least one
-            non-whitespace character to be valid.
+            The field name.
         value
-            The mandatory non-empty field value. This must contain at least one
-            non-whitespace character to be valid.
+            The field value.
         inline
             If [`True`][], the embed field may be shown "inline" on some
             Discord clients with other fields. If [`False`][], it is always placed
@@ -811,6 +809,10 @@ def add_field(self, name: str, value: str, *, inline: bool = False) -> Embed:
         """
         if self._fields is None:
             self._fields = []
+        if name is None:
+            name = ""
+        if value is None:
+            value = ""
         self._fields.append(EmbedField(name=name, value=value, inline=inline))
         return self
 
diff --git a/hikari/impl/entity_factory.py b/hikari/impl/entity_factory.py
index a81fe8d4..6bc9de76 100644
--- a/hikari/impl/entity_factory.py
+++ b/hikari/impl/entity_factory.py
@@ -1680,17 +1680,9 @@ def serialize_embed(  # noqa: C901 - Function too complex
 
                 if name is None:
                     raise TypeError(f"in embed.fields[{i}].name - cannot have `None`")
-                if not name:
-                    raise TypeError(f"in embed.fields[{i}].name - cannot have empty string")
-                if not name.strip():
-                    raise TypeError(f"in embed.fields[{i}].name - cannot have only whitespace")
 
                 if value is None:
                     raise TypeError(f"in embed.fields[{i}].value - cannot have `None`")
-                if not value:
-                    raise TypeError(f"in embed.fields[{i}].value - cannot have empty string")
-                if not value.strip():
-                    raise TypeError(f"in embed.fields[{i}].value - cannot have only whitespace")
 
                 # Name and value always have to be specified; we can always
                 # send a default `inline` value also just to keep this simpler.
diff --git a/hikari/impl/rest.py b/hikari/impl/rest.py
index 1fcf9c2c..73bd274f 100644
--- a/hikari/impl/rest.py
+++ b/hikari/impl/rest.py
@@ -988,6 +988,13 @@ async def _parse_ratelimits(
 
         body = self._loads(await response.read())
         assert isinstance(body, dict)
+        if "retry_after" not in body:
+            # This is most probably a Cloudflare ban, so just output the entire
+            # body to the console and abort the request.
+            raise errors.HTTPResponseError(
+                str(response.real_url), http.HTTPStatus.TOO_MANY_REQUESTS, response.headers, str(body), str(body)
+            )
+
         body_retry_after = float(body["retry_after"])
 
         if body.get("global", False) is True:


Test patch:
diff --git a/tests/hikari/impl/test_entity_factory.py b/tests/hikari/impl/test_entity_factory.py
index 67d116be..8c778539 100644
--- a/tests/hikari/impl/test_entity_factory.py
+++ b/tests/hikari/impl/test_entity_factory.py
@@ -2967,19 +2967,11 @@ def test_serialize_embed_with_null_attributes(self, entity_factory_impl):
         assert entity_factory_impl.serialize_embed(embed_models.Embed()) == ({}, [])
 
     @pytest.mark.parametrize(
-        "field_kwargs",
-        [
-            {"name": None, "value": "correct value"},
-            {"name": "", "value": "correct value"},
-            {"name": "    ", "value": "correct value"},
-            {"name": "correct value", "value": None},
-            {"name": "correct value", "value": ""},
-            {"name": "correct value", "value": "    "},
-        ],
+        "field_kwargs", [{"name": None, "value": "correct value"}, {"name": "correct value", "value": None}]
     )
     def test_serialize_embed_validators(self, entity_factory_impl, field_kwargs):
         embed_obj = embed_models.Embed()
-        embed_obj.add_field(**field_kwargs)
+        embed_obj._fields = [embed_models.EmbedField(**field_kwargs)]
         with pytest.raises(TypeError):
             entity_factory_impl.serialize_embed(embed_obj)
 
diff --git a/tests/hikari/test_embeds.py b/tests/hikari/test_embeds.py
index ee14e4d6..8aee13fa 100644
--- a/tests/hikari/test_embeds.py
+++ b/tests/hikari/test_embeds.py
@@ -109,3 +109,9 @@ def test_total_length_all(self):
         embed.add_field(name="field name 1", value="field value 2")
         embed.add_field(name="field name 3", value="field value 4")
         assert embed.total_length() == 88
+
+    def test_adding_field_with_none_values(self):
+        embed = embeds.Embed().add_field(name=None, value=None)
+        assert embed.total_length() == 0
+        assert embed.fields[0].name == ""
+        assert embed.fields[0].value == ""