Skip to content

[BUG] Twitch chat downloading fails #283

@uwu-crj

Description

@uwu-crj

Basic information

  • Program version: 0.2.8
  • Python version: 3.12.3
  • Operating system: Ubuntu

Describe the bug

As of a few hours ago (definitely fewer than 24), chat_downloader ceased operating entirely on Twitch, for either live streams or VODs.

Command/Code used

If running from the command line, provide the following:

  1. The command used (including the verbose tag, -v):
$ chat_downloader -v https://www.twitch.tv/dashducks 
  1. Output from the above command:
[DEBUG] Python version: 3.12.3 (main, Aug 14 2025, 17:47:21) [GCC 13.3.0]
[DEBUG] Program version: 0.2.8
[DEBUG] Initialisation parameters: {'headers': None, 'cookies': None, 'proxy': None}
[DEBUG] Created TwitchChatDownloader session.
[INFO] Site: twitch.tv
[DEBUG] Program parameters: {'url': 'https://www.twitch.tv/dashducks', 'start_time': None, 'end_time': None, 'max_attempts': 15, 'retry_timeout': None, 'interruptible_retry': True, 'timeout': None, 'inactivity_timeout': None, 'max_messages': None, 'message_groups': ['messages'], 'message_types': None, 'output': None, 'overwrite': True, 'sort_keys': True, 'indent': 4, 'format': 'twitch', 'format_file': None, 'chat_type': 'live', 'ignore': None, 'message_receive_timeout': 0.1, 'buffer_size': 4096}
[DEBUG] Starting new HTTPS connection (1): gql.twitch.tv:443
[DEBUG] https://gql.twitch.tv:443 "POST /gql HTTP/1.1" 200 165
[DEBUG] Session closed.
Traceback (most recent call last):
  File "/home/crj/venv-testing/bin/chat_downloader", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/crj/venv-testing/lib/python3.12/site-packages/chat_downloader/cli.py", line 194, in main
    run(**args.__dict__)
  File "/home/crj/venv-testing/lib/python3.12/site-packages/chat_downloader/chat_downloader.py", line 351, in run
    chat = downloader.get_chat(**chat_params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/crj/venv-testing/lib/python3.12/site-packages/chat_downloader/chat_downloader.py", line 224, in get_chat
    chat = get_chat(match, params)
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/crj/venv-testing/lib/python3.12/site-packages/chat_downloader/sites/twitch.py", line 1646, in _get_chat_by_stream_id
    return self.get_chat_by_stream_id(match.group('id'), params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/crj/venv-testing/lib/python3.12/site-packages/chat_downloader/sites/twitch.py", line 1659, in get_chat_by_stream_id
    stream_info = self._download_gql(query)[0]['data']['user']
                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^
KeyError: 'data'

Otherwise, if using the python module, provide the following:

I'm not including an example because the problem is reproducible via the command line, but I initially observed it via the Python API (with the same exception at the same point in the code).

Expected behavior

I expected the program to retrieve chat messages.

Screenshots

n/a

Additional context/information

I believe this is because Twitch has replaced all of their GQL persistedQuery hashes; yt-dlp and streamlink are also affected.

This means that _OPERATION_HASHES at twitch.py:842 needs updating. I would fix it myself, but I can't see how to determine the replacements.

Activity

babupipi

babupipi commented on Nov 11, 2025

@babupipi

twitch.py

Line 856
# 'ComscoreStreamingQuery': 'e1edae8122517d013405f237ffcc124515dc6ded82480a88daef69c83b53ac01',

Do this
'ComscoreStreamingQuery': 'e1edae8122517d013405f237ffcc124515dc6ded82480a88daef69c83b53ac01',

and

Line 1652

    query = [{
        'operationName': 'StreamMetadata',
        'variables': {'channelLogin': stream_id.lower()}
    }]

Do this

    query = [{
        'operationName': 'StreamMetadata',
        'variables': {'channelLogin': stream_id.lower()}
    },{
        'operationName': 'ComscoreStreamingQuery',
        'variables': {
            'channel': stream_id.lower(),
            'clipSlug': '',
            'isClip': False,
            'isLive': True,
            'isVodOrCollection': False,
            'vodID': '',
    }}]
gadi-playstream-gg

gadi-playstream-gg commented on Nov 11, 2025

@gadi-playstream-gg

having the same issue.

uwu-crj

uwu-crj commented on Nov 11, 2025

@uwu-crj
Author

Line 1652
[โ€ฆ]
Do this
[โ€ฆ]

Thanks โ€” that seems to fix acquisition from a live stream, but the problem remains for a VOD.

Acting on raw intuition based on the change you suggested for get_chat_by_stream_id I tried amending get_chat_by_vod_id to say:

        query = [{
            'operationName': 'VideoMetadata',
            'variables': {
                'channelLogin': '',
                'videoID': vod_id
            }
        },{
            'operationName': 'ComscoreStreamingQuery',
            'variables': {
                'channel': '',
                'clipSlug': '',
                'isClip': False,
                'isLive': False,
                'isVodOrCollection': False,
                'vodID': vod_id,
            }
        }]

โ€ฆbut that didn't work ยญยญโ€” when I instrumented it to print the GQL response, it read:

[
    {
        "errors": [
            {
                "message": "PersistedQueryNotFound"
            }
        ],
        "extensions": {
            "durationMilliseconds": 2,
            "operationName": "VideoMetadata",
            "requestID": "01K9S9E7XR72CQVP8SRYBJ647D"
        }
    },
    {
        "data": {},
        "extensions": {
            "durationMilliseconds": 2,
            "operationName": "ComscoreStreamingQuery",
            "requestID": "01K9S9E7XR72CQVP8SRYBJ647D"
        }
    }
sebastientromp

sebastientromp commented on Nov 12, 2025

@sebastientromp

Same issue here. Did anything change in the Twitch API?

I don't see anything in the changelog at https://dev.twitch.tv/docs/change-log/

nio-p

nio-p commented on Nov 12, 2025

@nio-p

@uwu-crj @sebastientromp

I changed the hash values โ€‹โ€‹of StreamMetadata and VideoMetadata as follows and it worked fine.
The relevant code is around line 842.

_OPERATION_HASHES = {
        'StreamMetadata': 'b57f9b910f8cd1a4659d894fe7550ccc81ec9052c01e438b290fd66a040b9b93',
        ...
        'VideoMetadata': '45111672eea2e507f8ba44d101a61862f9c56b11dee09a15634cb75cb9b9084d',
        ...
}
uwu-crj

uwu-crj commented on Nov 12, 2025

@uwu-crj
Author

@nio-p

I changed the hash values โ€‹โ€‹of StreamMetadata and VideoMetadata as follows and it worked fine. The relevant code is around line 842.

Excellent. Thanks!

That gets downloading chat for VODs working, butโ€ฆ did you test it on a live stream? I find it doesn't help there.

I think everything may work if I mash together your solution and @babupipi 's, though. ๐Ÿคž

uwu-crj

uwu-crj commented on Nov 12, 2025

@uwu-crj
Author

Update: Yes. A frankenpatch works for both live streams and VODs. (I've not tested it on clipsโ€ฆ)

843a844
>         
848c849
<         'VideoMetadata': '226edb3e692509f727fd56821f5653c05740242c82b0388883e0c0e75dcbf687',
---
>         'VideoMetadata': '45111672eea2e507f8ba44d101a61862f9c56b11dee09a15634cb75cb9b9084d',
852a854,855
>         'ComscoreStreamingQuery': 'e1edae8122517d013405f237ffcc124515dc6ded82480a88daef69c83b53ac01',
>         
856d858
<         # 'ComscoreStreamingQuery': 'e1edae8122517d013405f237ffcc124515dc6ded82480a88daef69c83b53ac01',
1654a1657,1666
>         },{
>             'operationName': 'ComscoreStreamingQuery',
>             'variables': {
>                 'channel': stream_id.lower(),
>                 'clipSlug': '',
>                 'isClip': False,
>                 'isLive': True,
>                 'isVodOrCollection': False,
>                 'vodID': '',
>             }
gadi-playstream-gg

gadi-playstream-gg commented on Nov 12, 2025

@gadi-playstream-gg

should I expect a new version soon?

Edwa42

Edwa42 commented on Nov 12, 2025

@Edwa42

should I expect a new version soon?

Personally I won't count on that, the the last update this lib received was in 2023. Thank you all for the patchwork solution tough.

pwcd

pwcd commented on Nov 13, 2025

@pwcd

Hi all,

By inspecting the network requests in Twitch, I found that most of these hashes have changed and, the live collecting one has also changed the required request variables.

Here should be the updated ones i found for _OPERATION_HASHES around line 842

    'ChatList_Badges': '838a7e0b47c09cac05f93ff081a9ff4f876b68f7624f0fc465fe30031e372fc2',
    'StreamMetadata': 'b57f9b910f8cd1a4659d894fe7550ccc81ec9052c01e438b290fd66a040b9b93',
    'BrowsePage_Popular': 'fb60a7f9b2fe8f9c9a080f41585bd4564bea9d3030f4d7cb8ab7f9e99b1cee67',
    'ChannelVideoShelvesQuery': 'a30af24ef449ab2e9be4a9c187ef14d294e4ec3041420d219b4c60fc9de7f27c',
    'ClipsCards__User': '90c33f5e6465122fba8f9371e2a97076f9ed06c6fed3788d002ab9eba8f91d88',
    'VideoMetadata': '45111672eea2e507f8ba44d101a61862f9c56b11dee09a15634cb75cb9b9084d',
    'FilterableVideoTower_Videos': '67004f7881e65c297936f32c75246470629557a393788fb5a69d6d9a25a8fd5f',
    'VideoCommentsByOffsetOrCursor': 'b70a3591ff0f4e0313d126c6a1502d79a1c02baebb288227c582044aa76adf6a',

For the live collecting it now seems for the query to require the variables to include "includeIsDJ": true.

Around line 1653 in twitch.py change it to this:

    query = [{
        'operationName': 'StreamMetadata',
        'variables': {
            'channelLogin': stream_id.lower(),
            'includeIsDJ': True
        }
    }]

After applying these changes, my live and VOD/clip collection are both working again.

Hope this helps!

sebastientromp

sebastientromp commented on Nov 14, 2025

@sebastientromp

should I expect a new version soon?

I published a new version (which is this repo's master + the changes suggested above) to sebastientromp-chat-downloader if you don't want to bother with creating a new version yourself and only need this update

uwu-crj

uwu-crj commented on Nov 14, 2025

@uwu-crj
Author

I published a new version (which is this repo's master + the changes suggested above) to sebastientromp-chat-downloader if you don't want to bother with creating a new version yourself and only need this update

A neat idea. Unfortunately, your patch somehow seems to have lost badge metadata for both VODs and live broadcasts.

gadi-playstream-gg

gadi-playstream-gg commented on Nov 14, 2025

@gadi-playstream-gg

should I expect a new version soon?

I published a new version (which is this repo's master + the changes suggested above) to sebastientromp-chat-downloader if you don't want to bother with creating a new version yourself and only need this update

what does it means? how can I get the latest changes?

nio-p

nio-p commented on Nov 14, 2025

@nio-p

@gadi-playstream-gg

Hello,

what does it means? how can I get the latest changes?

@sebastientromp has forked and released a new package.
The package name is "sebastientromp-chat-downloader."
For more information, see the repository below:
https://github.com/sebastientromp/chat-downloader

Furthermore, the original package has not been maintained since 2023.
Therefore, you will need to fork and maintain it yourself, use a volunteer-modified version (such as sebastientromp-chat-downloader.), or use a different package (e.g., https://github.com/lay295/TwitchDownloader).

fo5for

fo5for commented on Nov 15, 2025

@fo5for

I published a new version (which is this repo's master + the changes suggested above) to sebastientromp-chat-downloader if you don't want to bother with creating a new version yourself and only need this update

A neat idea. Unfortunately, your patch somehow seems to have lost badge metadata for both VODs and live broadcasts.

The metadata for global and channel-specific badges are now retrived by two separate operations, so you would need:

--- a/chat_downloader/sites/twitch.py
+++ b/chat_downloader/sites/twitch.py
@@ -788,7 +788,11 @@ def _update_badge_info(self, channel):
                 'channelLogin': channel
             }
         }]
+        gquery = [{
+            'operationName': 'GlobalBadges',
+        }]
         data = multi_get(self._download_gql(query), 0, 'data') or {}
+        data.update(multi_get(self._download_gql(gquery), 0, 'data') or {})
 
         badges = data.get('badges') or []
         user = multi_get(data, 'user', 'broadcastBadges') or []
@@ -840,7 +844,8 @@ def _parse_item(item, offset, channel_id=None):
         return info
 
     _OPERATION_HASHES = {
         'ChatList_Badges': '838a7e0b47c09cac05f93ff081a9ff4f876b68f7624f0fc465fe30031e372fc2',
+        'GlobalBadges': '9db27e18d61ee393ccfdec8c7d90f14f9a11266298c2e5eb808550b77d7bcdf6',
         'StreamMetadata': 'b57f9b910f8cd1a4659d894fe7550ccc81ec9052c01e438b290fd66a040b9b93',
         'BrowsePage_Popular': 'fb60a7f9b2fe8f9c9a080f41585bd4564bea9d3030f4d7cb8ab7f9e99b1cee67',
         'ChannelVideoShelvesQuery': 'a30af24ef449ab2e9be4a9c187ef14d294e4ec3041420d219b4c60fc9de7f27c',

@sebastientromp you may want to include that in your fork

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @sebastientromp@pwcd@nio-p@babupipi@fo5for

        Issue actions

          [BUG] Twitch chat downloading fails ยท Issue #283 ยท xenova/chat-downloader