prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  plugins.hls: recognize URLs with uppercase ".M3U8"
  ### Checklist
  
  - [X] This is a bug report and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)
  - [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)
  - [X] [I have checked the list of open and recently closed bug reports](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22bug%22)
  - [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)
  
  ### Streamlink version
  
  streamlink 6.2.1+29.gc82a8535
  
  ### Description
  
  Currently a URL with upper case M3U8, e.g. `https://example.com/live.M3U8` would not be recognized as an HLS URL.
  
  
  
  ### Debug log
  
  ```text
  >streamlink https://example.com/live.M3U8 best --loglevel=debug 
  [cli][debug] OS:         Windows 10
  [cli][debug] Python:     3.11.1
  [cli][debug] OpenSSL:    OpenSSL 1.1.1q  5 Jul 2022
  [cli][debug] Streamlink: 6.2.1+29.gc82a8535
  [cli][debug] Dependencies:
  [cli][debug]  certifi: 2023.5.7
  [cli][debug]  isodate: 0.6.1
  [cli][debug]  lxml: 4.9.2
  [cli][debug]  pycountry: 22.3.5
  [cli][debug]  pycryptodome: 3.16.0
  [cli][debug]  PySocks: 1.7.1
  [cli][debug]  requests: 2.31.0
  [cli][debug]  trio: 0.22.0
  [cli][debug]  trio-websocket: 0.9.2
  [cli][debug]  typing-extensions: 4.4.0
  [cli][debug]  urllib3: 1.26.15
  [cli][debug]  websocket-client: 1.5.1
  [cli][debug] Arguments:
  [cli][debug]  url=https://example.com/live.M3U8
  [cli][debug]  stream=['best']
  [cli][debug]  --loglevel=debug
  [cli][debug]  --player=mpv.exe
  [cli][debug]  --stream-segment-threads=10
  [cli][debug]  --hls-segment-queue-threshold=0.0
  error: No plugin can handle URL: https://example.com/live.M3U8
  ```
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `src/streamlink/plugins/dash.py`
  Content:
  ```
  1 import logging
  2 import re
  3 
  4 from streamlink.plugin import Plugin, pluginmatcher
  5 from streamlink.plugin.plugin import LOW_PRIORITY, parse_params, stream_weight
  6 from streamlink.stream.dash import DASHStream
  7 from streamlink.utils.url import update_scheme
  8 
  9 
  10 log = logging.getLogger(__name__)
  11 
  12 
  13 @pluginmatcher(re.compile(
  14     r"dash://(?P<url>\S+)(?:\s(?P<params>.+))?$",
  15 ))
  16 @pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(
  17     r"(?P<url>\S+\.mpd(?:\?\S*)?)(?:\s(?P<params>.+))?$",
  18 ))
  19 class MPEGDASH(Plugin):
  20     @classmethod
  21     def stream_weight(cls, stream):
  22         match = re.match(r"^(?:(.*)\+)?(?:a(\d+)k)$", stream)
  23         if match and match.group(1) and match.group(2):
  24             weight, group = stream_weight(match.group(1))
  25             weight += int(match.group(2))
  26             return weight, group
  27         elif match and match.group(2):
  28             return stream_weight(f"{match.group(2)}k")
  29         else:
  30             return stream_weight(stream)
  31 
  32     def _get_streams(self):
  33         data = self.match.groupdict()
  34         url = update_scheme("https://", data.get("url"), force=False)
  35         params = parse_params(data.get("params"))
  36         log.debug(f"URL={url}; params={params}")
  37 
  38         return DASHStream.parse_manifest(self.session, url, **params)
  39 
  40 
  41 __plugin__ = MPEGDASH
  42
  ```
  
  Path: `src/streamlink/plugins/hls.py`
  Content:
  ```
  1 import logging
  2 import re
  3 
  4 from streamlink.plugin import Plugin, pluginmatcher
  5 from streamlink.plugin.plugin import LOW_PRIORITY, parse_params
  6 from streamlink.stream.hls import HLSStream
  7 from streamlink.utils.url import update_scheme
  8 
  9 
  10 log = logging.getLogger(__name__)
  11 
  12 
  13 @pluginmatcher(re.compile(
  14     r"hls(?:variant)?://(?P<url>\S+)(?:\s(?P<params>.+))?$",
  15 ))
  16 @pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(
  17     r"(?P<url>\S+\.m3u8(?:\?\S*)?)(?:\s(?P<params>.+))?$",
  18 ))
  19 class HLSPlugin(Plugin):
  20     def _get_streams(self):
  21         data = self.match.groupdict()
  22         url = update_scheme("https://", data.get("url"), force=False)
  23         params = parse_params(data.get("params"))
  24         log.debug(f"URL={url}; params={params}")
  25 
  26         streams = HLSStream.parse_variant_playlist(self.session, url, **params)
  27 
  28         return streams or {"live": HLSStream(self.session, url, **params)}
  29 
  30 
  31 __plugin__ = HLSPlugin
  32
  ```
  --- 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()
  
  ```
  
golden_diff: diff --git a/src/streamlink/plugins/dash.py b/src/streamlink/plugins/dash.py
  --- a/src/streamlink/plugins/dash.py
  +++ b/src/streamlink/plugins/dash.py
  @@ -15,6 +15,7 @@
   ))
   @pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(
       r"(?P<url>\S+\.mpd(?:\?\S*)?)(?:\s(?P<params>.+))?$",
  +    re.IGNORECASE,
   ))
   class MPEGDASH(Plugin):
       @classmethod
  diff --git a/src/streamlink/plugins/hls.py b/src/streamlink/plugins/hls.py
  --- a/src/streamlink/plugins/hls.py
  +++ b/src/streamlink/plugins/hls.py
  @@ -15,6 +15,7 @@
   ))
   @pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(
       r"(?P<url>\S+\.m3u8(?:\?\S*)?)(?:\s(?P<params>.+))?$",
  +    re.IGNORECASE,
   ))
   class HLSPlugin(Plugin):
       def _get_streams(self):
  
verification_info: {"golden_diff": "diff --git a/src/streamlink/plugins/dash.py b/src/streamlink/plugins/dash.py\n--- a/src/streamlink/plugins/dash.py\n+++ b/src/streamlink/plugins/dash.py\n@@ -15,6 +15,7 @@\n ))\n @pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(\n     r\"(?P<url>\\S+\\.mpd(?:\\?\\S*)?)(?:\\s(?P<params>.+))?$\",\n+    re.IGNORECASE,\n ))\n class MPEGDASH(Plugin):\n     @classmethod\ndiff --git a/src/streamlink/plugins/hls.py b/src/streamlink/plugins/hls.py\n--- a/src/streamlink/plugins/hls.py\n+++ b/src/streamlink/plugins/hls.py\n@@ -15,6 +15,7 @@\n ))\n @pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(\n     r\"(?P<url>\\S+\\.m3u8(?:\\?\\S*)?)(?:\\s(?P<params>.+))?$\",\n+    re.IGNORECASE,\n ))\n class HLSPlugin(Plugin):\n     def _get_streams(self):\n", "issue": "plugins.hls: recognize URLs with uppercase \".M3U8\"\n### Checklist\n\n- [X] This is a bug report and not [a different kind of issue](https://github.com/streamlink/streamlink/issues/new/choose)\n- [X] [I have read the contribution guidelines](https://github.com/streamlink/streamlink/blob/master/CONTRIBUTING.md#contributing-to-streamlink)\n- [X] [I have checked the list of open and recently closed bug reports](https://github.com/streamlink/streamlink/issues?q=is%3Aissue+label%3A%22bug%22)\n- [X] [I have checked the commit log of the master branch](https://github.com/streamlink/streamlink/commits/master)\n\n### Streamlink version\n\nstreamlink 6.2.1+29.gc82a8535\n\n### Description\n\nCurrently a URL with upper case M3U8, e.g. `https://example.com/live.M3U8` would not be recognized as an HLS URL.\r\n\r\n\n\n### Debug log\n\n```text\n>streamlink https://example.com/live.M3U8 best --loglevel=debug \r\n[cli][debug] OS:         Windows 10\r\n[cli][debug] Python:     3.11.1\r\n[cli][debug] OpenSSL:    OpenSSL 1.1.1q  5 Jul 2022\r\n[cli][debug] Streamlink: 6.2.1+29.gc82a8535\r\n[cli][debug] Dependencies:\r\n[cli][debug]  certifi: 2023.5.7\r\n[cli][debug]  isodate: 0.6.1\r\n[cli][debug]  lxml: 4.9.2\r\n[cli][debug]  pycountry: 22.3.5\r\n[cli][debug]  pycryptodome: 3.16.0\r\n[cli][debug]  PySocks: 1.7.1\r\n[cli][debug]  requests: 2.31.0\r\n[cli][debug]  trio: 0.22.0\r\n[cli][debug]  trio-websocket: 0.9.2\r\n[cli][debug]  typing-extensions: 4.4.0\r\n[cli][debug]  urllib3: 1.26.15\r\n[cli][debug]  websocket-client: 1.5.1\r\n[cli][debug] Arguments:\r\n[cli][debug]  url=https://example.com/live.M3U8\r\n[cli][debug]  stream=['best']\r\n[cli][debug]  --loglevel=debug\r\n[cli][debug]  --player=mpv.exe\r\n[cli][debug]  --stream-segment-threads=10\r\n[cli][debug]  --hls-segment-queue-threshold=0.0\r\nerror: No plugin can handle URL: https://example.com/live.M3U8\n```\n\n", "before_files": [{"content": "import logging\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.plugin import LOW_PRIORITY, parse_params, stream_weight\nfrom streamlink.stream.dash import DASHStream\nfrom streamlink.utils.url import update_scheme\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"dash://(?P<url>\\S+)(?:\\s(?P<params>.+))?$\",\n))\n@pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(\n    r\"(?P<url>\\S+\\.mpd(?:\\?\\S*)?)(?:\\s(?P<params>.+))?$\",\n))\nclass MPEGDASH(Plugin):\n    @classmethod\n    def stream_weight(cls, stream):\n        match = re.match(r\"^(?:(.*)\\+)?(?:a(\\d+)k)$\", stream)\n        if match and match.group(1) and match.group(2):\n            weight, group = stream_weight(match.group(1))\n            weight += int(match.group(2))\n            return weight, group\n        elif match and match.group(2):\n            return stream_weight(f\"{match.group(2)}k\")\n        else:\n            return stream_weight(stream)\n\n    def _get_streams(self):\n        data = self.match.groupdict()\n        url = update_scheme(\"https://\", data.get(\"url\"), force=False)\n        params = parse_params(data.get(\"params\"))\n        log.debug(f\"URL={url}; params={params}\")\n\n        return DASHStream.parse_manifest(self.session, url, **params)\n\n\n__plugin__ = MPEGDASH\n", "path": "src/streamlink/plugins/dash.py"}, {"content": "import logging\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.plugin import LOW_PRIORITY, parse_params\nfrom streamlink.stream.hls import HLSStream\nfrom streamlink.utils.url import update_scheme\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"hls(?:variant)?://(?P<url>\\S+)(?:\\s(?P<params>.+))?$\",\n))\n@pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(\n    r\"(?P<url>\\S+\\.m3u8(?:\\?\\S*)?)(?:\\s(?P<params>.+))?$\",\n))\nclass HLSPlugin(Plugin):\n    def _get_streams(self):\n        data = self.match.groupdict()\n        url = update_scheme(\"https://\", data.get(\"url\"), force=False)\n        params = parse_params(data.get(\"params\"))\n        log.debug(f\"URL={url}; params={params}\")\n\n        streams = HLSStream.parse_variant_playlist(self.session, url, **params)\n\n        return streams or {\"live\": HLSStream(self.session, url, **params)}\n\n\n__plugin__ = HLSPlugin\n", "path": "src/streamlink/plugins/hls.py"}], "after_files": [{"content": "import logging\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.plugin import LOW_PRIORITY, parse_params, stream_weight\nfrom streamlink.stream.dash import DASHStream\nfrom streamlink.utils.url import update_scheme\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"dash://(?P<url>\\S+)(?:\\s(?P<params>.+))?$\",\n))\n@pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(\n    r\"(?P<url>\\S+\\.mpd(?:\\?\\S*)?)(?:\\s(?P<params>.+))?$\",\n    re.IGNORECASE,\n))\nclass MPEGDASH(Plugin):\n    @classmethod\n    def stream_weight(cls, stream):\n        match = re.match(r\"^(?:(.*)\\+)?(?:a(\\d+)k)$\", stream)\n        if match and match.group(1) and match.group(2):\n            weight, group = stream_weight(match.group(1))\n            weight += int(match.group(2))\n            return weight, group\n        elif match and match.group(2):\n            return stream_weight(f\"{match.group(2)}k\")\n        else:\n            return stream_weight(stream)\n\n    def _get_streams(self):\n        data = self.match.groupdict()\n        url = update_scheme(\"https://\", data.get(\"url\"), force=False)\n        params = parse_params(data.get(\"params\"))\n        log.debug(f\"URL={url}; params={params}\")\n\n        return DASHStream.parse_manifest(self.session, url, **params)\n\n\n__plugin__ = MPEGDASH\n", "path": "src/streamlink/plugins/dash.py"}, {"content": "import logging\nimport re\n\nfrom streamlink.plugin import Plugin, pluginmatcher\nfrom streamlink.plugin.plugin import LOW_PRIORITY, parse_params\nfrom streamlink.stream.hls import HLSStream\nfrom streamlink.utils.url import update_scheme\n\n\nlog = logging.getLogger(__name__)\n\n\n@pluginmatcher(re.compile(\n    r\"hls(?:variant)?://(?P<url>\\S+)(?:\\s(?P<params>.+))?$\",\n))\n@pluginmatcher(priority=LOW_PRIORITY, pattern=re.compile(\n    r\"(?P<url>\\S+\\.m3u8(?:\\?\\S*)?)(?:\\s(?P<params>.+))?$\",\n    re.IGNORECASE,\n))\nclass HLSPlugin(Plugin):\n    def _get_streams(self):\n        data = self.match.groupdict()\n        url = update_scheme(\"https://\", data.get(\"url\"), force=False)\n        params = parse_params(data.get(\"params\"))\n        log.debug(f\"URL={url}; params={params}\")\n\n        streams = HLSStream.parse_variant_playlist(self.session, url, **params)\n\n        return streams or {\"live\": HLSStream(self.session, url, **params)}\n\n\n__plugin__ = HLSPlugin\n", "path": "src/streamlink/plugins/hls.py"}]}