We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Emojis are not valid if they have a variant selector character attached
### Summary

Emojis are not valid if they are prefixed with a variant selector character. This is a hidden character that is used as prefix for the emoji (more information [here](https://stackoverflow.com/questions/38100329/what-does-u-ufe0f-in-an-emoji-mean-is-it-the-same-if-i-delete-it)).

### Steps to reproduce

[![Open in Streamlit Cloud](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://issues.streamlitapp.com/?issue=gh-5564)

Code snippet:

```python
st.error("This is an error", icon="🚨") # Works fine
st.error("This is an error", icon="️🚨") # Throws an error
```

The reason is that the second example is prefix with this hidden unicode character: `%uFE0F`:

```python
st.write(len("🚨"))  # 1
st.write(len("️🚨"))  # 2
```

**Expected behavior:**

Should not raise an exception.

**Actual behavior:**

Raises a `StreamlitAPIException` if used for `st.error`, `st.info`, ...

### Is this a regression?

no

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/streamlit/string_util.py`
Content:
```
1 # Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 
15 import re
16 import textwrap
17 from datetime import datetime
18 from typing import TYPE_CHECKING, Any, Tuple, cast
19 
20 from streamlit.emojis import ALL_EMOJIS
21 from streamlit.errors import StreamlitAPIException
22 
23 if TYPE_CHECKING:
24     from streamlit.type_util import SupportsStr
25 
26 
27 # The ESCAPED_EMOJI list is sorted in descending order to make that longer emoji appear
28 # first in the regex compiled below. This ensures that we grab the full emoji in a
29 # multi-character emoji sequence that starts with a shorter emoji (emoji are weird...).
30 ESCAPED_EMOJI = [re.escape(e) for e in sorted(ALL_EMOJIS, reverse=True)]
31 EMOJI_EXTRACTION_REGEX = re.compile(f"^({'|'.join(ESCAPED_EMOJI)})[_ -]*(.*)")
32 
33 
34 def decode_ascii(string: bytes) -> str:
35     """Decodes a string as ascii."""
36     return string.decode("ascii")
37 
38 
39 def clean_text(text: "SupportsStr") -> str:
40     """Convert an object to text, dedent it, and strip whitespace."""
41     return textwrap.dedent(str(text)).strip()
42 
43 
44 def is_emoji(text: str) -> bool:
45     """Check if input string is a valid emoji."""
46     return text in ALL_EMOJIS
47 
48 
49 def extract_leading_emoji(text: str) -> Tuple[str, str]:
50     """Return a tuple containing the first emoji found in the given string and
51     the rest of the string (minus an optional separator between the two).
52     """
53     re_match = re.search(EMOJI_EXTRACTION_REGEX, text)
54     if re_match is None:
55         return "", text
56 
57     # This cast to Any+type annotation weirdness is done because
58     # cast(re.Match[str], ...) explodes at runtime since Python interprets it
59     # as an attempt to index into re.Match instead of as a type annotation.
60     re_match: re.Match[str] = cast(Any, re_match)
61     return re_match.group(1), re_match.group(2)
62 
63 
64 def escape_markdown(raw_string: str) -> str:
65     """Returns a new string which escapes all markdown metacharacters.
66 
67     Args
68     ----
69     raw_string : str
70         A string, possibly with markdown metacharacters, e.g. "1 * 2"
71 
72     Returns
73     -------
74     A string with all metacharacters escaped.
75 
76     Examples
77     --------
78     ::
79         escape_markdown("1 * 2") -> "1 \\* 2"
80     """
81     metacharacters = ["\\", "*", "-", "=", "`", "!", "#", "|"]
82     result = raw_string
83     for character in metacharacters:
84         result = result.replace(character, "\\" + character)
85     return result
86 
87 
88 TEXTCHARS = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})
89 
90 
91 def is_binary_string(inp):
92     """Guess if an input bytesarray can be encoded as a string."""
93     # From https://stackoverflow.com/a/7392391
94     return bool(inp.translate(None, TEXTCHARS))
95 
96 
97 def clean_filename(name: str) -> str:
98     """
99     Taken from https://github.com/django/django/blob/196a99da5d9c4c33a78259a58d38fb114a4d2ee8/django/utils/text.py#L225-L238
100 
101     Return the given string converted to a string that can be used for a clean
102     filename. Remove leading and trailing spaces; convert other spaces to
103     underscores; and remove anything that is not an alphanumeric, dash,
104     underscore, or dot.
105     """
106     s = str(name).strip().replace(" ", "_")
107     s = re.sub(r"(?u)[^-\w.]", "", s)
108 
109     if s in {"", ".", ".."}:
110         raise StreamlitAPIException("Could not derive file name from '%s'" % name)
111     return s
112 
113 
114 def snake_case_to_camel_case(snake_case_string: str) -> str:
115     """Transform input string from snake_case to CamelCase."""
116     words = snake_case_string.split("_")
117     capitalized_words_arr = []
118 
119     for word in words:
120         if word:
121             try:
122                 capitalized_words_arr.append(word.title())
123             except Exception:
124                 capitalized_words_arr.append(word)
125     return "".join(capitalized_words_arr)
126 
127 
128 def append_date_time_to_string(input_string: str) -> str:
129     """Append datetime string to input string.
130     Returns datetime string if input is empty string.
131     """
132     now = datetime.now()
133 
134     if not input_string:
135         return now.strftime("%Y-%m-%d_%H-%M-%S")
136     else:
137         return f'{input_string}_{now.strftime("%Y-%m-%d_%H-%M-%S")}'
138 
139 
140 def generate_download_filename_from_title(title_string: str) -> str:
141     """Generated download filename from page title string."""
142 
143     title_string = title_string.replace(" · Streamlit", "")
144     file_name_string = clean_filename(title_string)
145     title_string = snake_case_to_camel_case(file_name_string)
146     return append_date_time_to_string(title_string)
147 
148 
149 def simplify_number(num: int) -> str:
150     """Simplifies number into Human readable format, returns str"""
151     num_converted = float("{:.2g}".format(num))
152     magnitude = 0
153     while abs(num_converted) >= 1000:
154         magnitude += 1
155         num_converted /= 1000.0
156     return "{}{}".format(
157         "{:f}".format(num_converted).rstrip("0").rstrip("."),
158         ["", "k", "m", "b", "t"][magnitude],
159     )
160
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/streamlit/string_util.py b/lib/streamlit/string_util.py
--- a/lib/streamlit/string_util.py
+++ b/lib/streamlit/string_util.py
@@ -43,7 +43,7 @@
 
 def is_emoji(text: str) -> bool:
     """Check if input string is a valid emoji."""
-    return text in ALL_EMOJIS
+    return text.replace("\U0000FE0F", "") in ALL_EMOJIS
 
 
 def extract_leading_emoji(text: str) -> Tuple[str, str]:

{"golden_diff": "diff --git a/lib/streamlit/string_util.py b/lib/streamlit/string_util.py\n--- a/lib/streamlit/string_util.py\n+++ b/lib/streamlit/string_util.py\n@@ -43,7 +43,7 @@\n \n def is_emoji(text: str) -> bool:\n     \"\"\"Check if input string is a valid emoji.\"\"\"\n-    return text in ALL_EMOJIS\n+    return text.replace(\"\\U0000FE0F\", \"\") in ALL_EMOJIS\n \n \n def extract_leading_emoji(text: str) -> Tuple[str, str]:\n", "issue": "Emojis are not valid if they have a variant selector character attached\n### Summary\r\n\r\nEmojis are not valid if they are prefixed with a variant selector character. This is a hidden character that is used as prefix for the emoji (more information [here](https://stackoverflow.com/questions/38100329/what-does-u-ufe0f-in-an-emoji-mean-is-it-the-same-if-i-delete-it)).\r\n\r\n### Steps to reproduce\r\n\r\n[![Open in Streamlit Cloud](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://issues.streamlitapp.com/?issue=gh-5564)\r\n\r\nCode snippet:\r\n\r\n```python\r\nst.error(\"This is an error\", icon=\"\ud83d\udea8\") # Works fine\r\nst.error(\"This is an error\", icon=\"\ufe0f\ud83d\udea8\") # Throws an error\r\n```\r\n\r\nThe reason is that the second example is prefix with this hidden unicode character: `%uFE0F`:\r\n\r\n```python\r\nst.write(len(\"\ud83d\udea8\"))  # 1\r\nst.write(len(\"\ufe0f\ud83d\udea8\"))  # 2\r\n```\r\n\r\n**Expected behavior:**\r\n\r\nShould not raise an exception.\r\n\r\n**Actual behavior:**\r\n\r\nRaises a `StreamlitAPIException` if used for `st.error`, `st.info`, ...\r\n\r\n### Is this a regression?\r\n\r\nno\n", "before_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\nimport textwrap\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Any, Tuple, cast\n\nfrom streamlit.emojis import ALL_EMOJIS\nfrom streamlit.errors import StreamlitAPIException\n\nif TYPE_CHECKING:\n    from streamlit.type_util import SupportsStr\n\n\n# The ESCAPED_EMOJI list is sorted in descending order to make that longer emoji appear\n# first in the regex compiled below. This ensures that we grab the full emoji in a\n# multi-character emoji sequence that starts with a shorter emoji (emoji are weird...).\nESCAPED_EMOJI = [re.escape(e) for e in sorted(ALL_EMOJIS, reverse=True)]\nEMOJI_EXTRACTION_REGEX = re.compile(f\"^({'|'.join(ESCAPED_EMOJI)})[_ -]*(.*)\")\n\n\ndef decode_ascii(string: bytes) -> str:\n    \"\"\"Decodes a string as ascii.\"\"\"\n    return string.decode(\"ascii\")\n\n\ndef clean_text(text: \"SupportsStr\") -> str:\n    \"\"\"Convert an object to text, dedent it, and strip whitespace.\"\"\"\n    return textwrap.dedent(str(text)).strip()\n\n\ndef is_emoji(text: str) -> bool:\n    \"\"\"Check if input string is a valid emoji.\"\"\"\n    return text in ALL_EMOJIS\n\n\ndef extract_leading_emoji(text: str) -> Tuple[str, str]:\n    \"\"\"Return a tuple containing the first emoji found in the given string and\n    the rest of the string (minus an optional separator between the two).\n    \"\"\"\n    re_match = re.search(EMOJI_EXTRACTION_REGEX, text)\n    if re_match is None:\n        return \"\", text\n\n    # This cast to Any+type annotation weirdness is done because\n    # cast(re.Match[str], ...) explodes at runtime since Python interprets it\n    # as an attempt to index into re.Match instead of as a type annotation.\n    re_match: re.Match[str] = cast(Any, re_match)\n    return re_match.group(1), re_match.group(2)\n\n\ndef escape_markdown(raw_string: str) -> str:\n    \"\"\"Returns a new string which escapes all markdown metacharacters.\n\n    Args\n    ----\n    raw_string : str\n        A string, possibly with markdown metacharacters, e.g. \"1 * 2\"\n\n    Returns\n    -------\n    A string with all metacharacters escaped.\n\n    Examples\n    --------\n    ::\n        escape_markdown(\"1 * 2\") -> \"1 \\\\* 2\"\n    \"\"\"\n    metacharacters = [\"\\\\\", \"*\", \"-\", \"=\", \"`\", \"!\", \"#\", \"|\"]\n    result = raw_string\n    for character in metacharacters:\n        result = result.replace(character, \"\\\\\" + character)\n    return result\n\n\nTEXTCHARS = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})\n\n\ndef is_binary_string(inp):\n    \"\"\"Guess if an input bytesarray can be encoded as a string.\"\"\"\n    # From https://stackoverflow.com/a/7392391\n    return bool(inp.translate(None, TEXTCHARS))\n\n\ndef clean_filename(name: str) -> str:\n    \"\"\"\n    Taken from https://github.com/django/django/blob/196a99da5d9c4c33a78259a58d38fb114a4d2ee8/django/utils/text.py#L225-L238\n\n    Return the given string converted to a string that can be used for a clean\n    filename. Remove leading and trailing spaces; convert other spaces to\n    underscores; and remove anything that is not an alphanumeric, dash,\n    underscore, or dot.\n    \"\"\"\n    s = str(name).strip().replace(\" \", \"_\")\n    s = re.sub(r\"(?u)[^-\\w.]\", \"\", s)\n\n    if s in {\"\", \".\", \"..\"}:\n        raise StreamlitAPIException(\"Could not derive file name from '%s'\" % name)\n    return s\n\n\ndef snake_case_to_camel_case(snake_case_string: str) -> str:\n    \"\"\"Transform input string from snake_case to CamelCase.\"\"\"\n    words = snake_case_string.split(\"_\")\n    capitalized_words_arr = []\n\n    for word in words:\n        if word:\n            try:\n                capitalized_words_arr.append(word.title())\n            except Exception:\n                capitalized_words_arr.append(word)\n    return \"\".join(capitalized_words_arr)\n\n\ndef append_date_time_to_string(input_string: str) -> str:\n    \"\"\"Append datetime string to input string.\n    Returns datetime string if input is empty string.\n    \"\"\"\n    now = datetime.now()\n\n    if not input_string:\n        return now.strftime(\"%Y-%m-%d_%H-%M-%S\")\n    else:\n        return f'{input_string}_{now.strftime(\"%Y-%m-%d_%H-%M-%S\")}'\n\n\ndef generate_download_filename_from_title(title_string: str) -> str:\n    \"\"\"Generated download filename from page title string.\"\"\"\n\n    title_string = title_string.replace(\" \u00b7 Streamlit\", \"\")\n    file_name_string = clean_filename(title_string)\n    title_string = snake_case_to_camel_case(file_name_string)\n    return append_date_time_to_string(title_string)\n\n\ndef simplify_number(num: int) -> str:\n    \"\"\"Simplifies number into Human readable format, returns str\"\"\"\n    num_converted = float(\"{:.2g}\".format(num))\n    magnitude = 0\n    while abs(num_converted) >= 1000:\n        magnitude += 1\n        num_converted /= 1000.0\n    return \"{}{}\".format(\n        \"{:f}\".format(num_converted).rstrip(\"0\").rstrip(\".\"),\n        [\"\", \"k\", \"m\", \"b\", \"t\"][magnitude],\n    )\n", "path": "lib/streamlit/string_util.py"}], "after_files": [{"content": "# Copyright (c) Streamlit Inc. (2018-2022) Snowflake Inc. (2022)\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport re\nimport textwrap\nfrom datetime import datetime\nfrom typing import TYPE_CHECKING, Any, Tuple, cast\n\nfrom streamlit.emojis import ALL_EMOJIS\nfrom streamlit.errors import StreamlitAPIException\n\nif TYPE_CHECKING:\n    from streamlit.type_util import SupportsStr\n\n\n# The ESCAPED_EMOJI list is sorted in descending order to make that longer emoji appear\n# first in the regex compiled below. This ensures that we grab the full emoji in a\n# multi-character emoji sequence that starts with a shorter emoji (emoji are weird...).\nESCAPED_EMOJI = [re.escape(e) for e in sorted(ALL_EMOJIS, reverse=True)]\nEMOJI_EXTRACTION_REGEX = re.compile(f\"^({'|'.join(ESCAPED_EMOJI)})[_ -]*(.*)\")\n\n\ndef decode_ascii(string: bytes) -> str:\n    \"\"\"Decodes a string as ascii.\"\"\"\n    return string.decode(\"ascii\")\n\n\ndef clean_text(text: \"SupportsStr\") -> str:\n    \"\"\"Convert an object to text, dedent it, and strip whitespace.\"\"\"\n    return textwrap.dedent(str(text)).strip()\n\n\ndef is_emoji(text: str) -> bool:\n    \"\"\"Check if input string is a valid emoji.\"\"\"\n    return text.replace(\"\\U0000FE0F\", \"\") in ALL_EMOJIS\n\n\ndef extract_leading_emoji(text: str) -> Tuple[str, str]:\n    \"\"\"Return a tuple containing the first emoji found in the given string and\n    the rest of the string (minus an optional separator between the two).\n    \"\"\"\n    re_match = re.search(EMOJI_EXTRACTION_REGEX, text)\n    if re_match is None:\n        return \"\", text\n\n    # This cast to Any+type annotation weirdness is done because\n    # cast(re.Match[str], ...) explodes at runtime since Python interprets it\n    # as an attempt to index into re.Match instead of as a type annotation.\n    re_match: re.Match[str] = cast(Any, re_match)\n    return re_match.group(1), re_match.group(2)\n\n\ndef escape_markdown(raw_string: str) -> str:\n    \"\"\"Returns a new string which escapes all markdown metacharacters.\n\n    Args\n    ----\n    raw_string : str\n        A string, possibly with markdown metacharacters, e.g. \"1 * 2\"\n\n    Returns\n    -------\n    A string with all metacharacters escaped.\n\n    Examples\n    --------\n    ::\n        escape_markdown(\"1 * 2\") -> \"1 \\\\* 2\"\n    \"\"\"\n    metacharacters = [\"\\\\\", \"*\", \"-\", \"=\", \"`\", \"!\", \"#\", \"|\"]\n    result = raw_string\n    for character in metacharacters:\n        result = result.replace(character, \"\\\\\" + character)\n    return result\n\n\nTEXTCHARS = bytearray({7, 8, 9, 10, 12, 13, 27} | set(range(0x20, 0x100)) - {0x7F})\n\n\ndef is_binary_string(inp):\n    \"\"\"Guess if an input bytesarray can be encoded as a string.\"\"\"\n    # From https://stackoverflow.com/a/7392391\n    return bool(inp.translate(None, TEXTCHARS))\n\n\ndef clean_filename(name: str) -> str:\n    \"\"\"\n    Taken from https://github.com/django/django/blob/196a99da5d9c4c33a78259a58d38fb114a4d2ee8/django/utils/text.py#L225-L238\n\n    Return the given string converted to a string that can be used for a clean\n    filename. Remove leading and trailing spaces; convert other spaces to\n    underscores; and remove anything that is not an alphanumeric, dash,\n    underscore, or dot.\n    \"\"\"\n    s = str(name).strip().replace(\" \", \"_\")\n    s = re.sub(r\"(?u)[^-\\w.]\", \"\", s)\n\n    if s in {\"\", \".\", \"..\"}:\n        raise StreamlitAPIException(\"Could not derive file name from '%s'\" % name)\n    return s\n\n\ndef snake_case_to_camel_case(snake_case_string: str) -> str:\n    \"\"\"Transform input string from snake_case to CamelCase.\"\"\"\n    words = snake_case_string.split(\"_\")\n    capitalized_words_arr = []\n\n    for word in words:\n        if word:\n            try:\n                capitalized_words_arr.append(word.title())\n            except Exception:\n                capitalized_words_arr.append(word)\n    return \"\".join(capitalized_words_arr)\n\n\ndef append_date_time_to_string(input_string: str) -> str:\n    \"\"\"Append datetime string to input string.\n    Returns datetime string if input is empty string.\n    \"\"\"\n    now = datetime.now()\n\n    if not input_string:\n        return now.strftime(\"%Y-%m-%d_%H-%M-%S\")\n    else:\n        return f'{input_string}_{now.strftime(\"%Y-%m-%d_%H-%M-%S\")}'\n\n\ndef generate_download_filename_from_title(title_string: str) -> str:\n    \"\"\"Generated download filename from page title string.\"\"\"\n\n    title_string = title_string.replace(\" \u00b7 Streamlit\", \"\")\n    file_name_string = clean_filename(title_string)\n    title_string = snake_case_to_camel_case(file_name_string)\n    return append_date_time_to_string(title_string)\n\n\ndef simplify_number(num: int) -> str:\n    \"\"\"Simplifies number into Human readable format, returns str\"\"\"\n    num_converted = float(\"{:.2g}\".format(num))\n    magnitude = 0\n    while abs(num_converted) >= 1000:\n        magnitude += 1\n        num_converted /= 1000.0\n    return \"{}{}\".format(\n        \"{:f}\".format(num_converted).rstrip(\"0\").rstrip(\".\"),\n        [\"\", \"k\", \"m\", \"b\", \"t\"][magnitude],\n    )\n", "path": "lib/streamlit/string_util.py"}]}