Skip to content

Hermes WebSocket API (Fixes #722)#728

Open
mpforce1 wants to merge 3 commits intordavydov:masterfrom
mpforce1:hermes
Open

Hermes WebSocket API (Fixes #722)#728
mpforce1 wants to merge 3 commits intordavydov:masterfrom
mpforce1:hermes

Conversation

@mpforce1
Copy link

Description

Implements an integration with the new Hermes API. As per issue #722 it seems like Twitch has finally finished A/B testing the new Hermes API. This PR adds a new WebSocket based integration with this API. It acts as a drop in replacement for the older PubSub WebSocket integration.

As part of the implementation some of the handling of PubSub Messages has been abstracted. This was done to avoid duplicating the handling logic in both integrations.

I've added a new setting to the miner constructor use_hermes. It defaults to False for now to avoid changing current behaviour for most users. In the future this might be removed if Twitch drops the old PubSub API.

I've opened this PR as draft as I'm currently seeing how it acts in the long term. This is because the reconnection logic in the new integration is different and I want to make sure it works as designed. To that end, it'd be really useful if some other community members could try out this branch and see if they run into any issues. In particular, I'd like to know:

  • If anyone runs into any cases where some topics don't end up getting subscribed to or they get dropped at some point later, for example during reconnection.
  • If anyone finds a case where a client should reconnect but no new client is actually created.
  • If anyone finds issues when the pool manages multiple clients, for example if the pool leaves a dangling connection or more clients are created than are actually needed.
  • If I've introduced a regression with the old PubSub integration. This can be tested by setting user_hermes to False or omitting it from the configuration.

I've tested all of this locally but more users testing in more varied setups should help catch any edge cases.

Fixes #722

Type of change

  • New feature (non-breaking change which adds functionality)

How Has This Been Tested?

Tested on my local setup. I'm currently running a long running test to check reconnection logic. However, my test setup only connects to a few streamers.

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented on my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (README.md)
  • My changes generate no new warnings
  • Any dependent changes have been updated in requirements.txt

@Gamerns10s
Copy link

Have been using it for around 48+ hours and only encountered "websocket closed None : None error" and "websocket error 104 connection closed by peer " error otherwise all features of the code are working perfectly fine ( excluding betting and drops which are still yet to test)
FYI:- I am on Linux x64 vps

@mpforce1
Copy link
Author

Have been using it for around 48+ hours and only encountered "websocket closed None : None error" and "websocket error 104 connection closed by peer " error otherwise all features of the code are working perfectly fine ( excluding betting and drops which are still yet to test) FYI:- I am on Linux x64 vps

Hey, thanks for your help!

The websocket closed None : None error message is normal. The Nones there are the status code and reason text. None generally comes through there if the WebSocket gets closed locally for any reason, there should be something more useful elsewhere in the logs that'll show up as an error if there's a problem. I might downgrade it to a DEBUG since it's not something you should normally have to worry about., although the original WebSocket logs it as INFO so we'll see.

The other message is probably fine. 104s can happen for a number of reasons but if it's reconnecting without issues I wouldn't worry about it.

@Gamerns10s
Copy link

For the None : None errors I just looked into another of my Miner which is on pubserver and noticed that actually the twitch servers was having some errors as both of the miners (one on hermes and one on pubserver) were disconnected at the same time which means that it isn't actually any code issue

@G0KU0
Copy link

G0KU0 commented Sep 30, 2025

how to download?

@mpforce1
Copy link
Author

how to download?

You can clone my branch here. No automated builds for this one since it's not an official release.

@G0KU0
Copy link

G0KU0 commented Sep 30, 2025

how to download?

You can clone my branch here. No automated builds for this one since it's not an official release.

I have a question: If I upload this to rander.com after I've linked it to my Twitch account, why does it ask me to link it again if I've already linked it?

@mpforce1
Copy link
Author

I have a question: If I upload this to rander.com after I've linked it to my Twitch account, why does it ask me to link it again if I've already linked it?

Sorry, I have no idea.

@G0KU0
Copy link

G0KU0 commented Sep 30, 2025

If I modify this part, will it work the same way?
username=os.environ.get('username'),
password=os.environ.get('password'),

@Gamerns10s
Copy link

Please try to use chatgpt for code modifying questions or just open a discussion or issue in the main repo. Also do read the readme file thoroughly it explains everything.
We are trying to keep conversation related to the actual issue here 🙂

@Gamerns10s
Copy link

Gamerns10s commented Sep 30, 2025

Update: There might be an issue somewhere related to betting cuz some of the best are getting logged twice that means maybe they are being processed twice (there might be a chance that the streamer actually made 2 different predictions back to back not sure though but still thought to drop my observations here)
@mpforce1
IMG_20251001_010110

@mpforce1
Copy link
Author

Update: There might be an issue somewhere related to betting cuz some of the best are getting logged twice that means maybe they are being processed twice (there might be a chance that the streamer actually made 2 different predictions back to back not sure though but still thought to drop my observations here)

Yeah, that looks like 2 different predictions with the same name happening close together. Might be worth checking the stream to see if it happens again.

@Gamerns10s
Copy link

I checked the stream and the predictions were actually made so not any issue about that. But the disconnections are a lot frequent as compared to pubsub like i disconnected 7-8 times in a 12 hours run and the most frequent was on a gap of like 35-40 mins only

@mpforce1
Copy link
Author

mpforce1 commented Oct 1, 2025

But the disconnections are a lot frequent as compared to pubsub like i disconnected 7-8 times in a 12 hours run and the most frequent was on a gap of like 35-40 mins only

I've seen an increase in reconnects too, not to the same degree but then I only run with a single client in the pool. I think this is something they've built into the new API to better distribute server load but no way to know for sure. As long as it reconnects without issue there should be no problem.

For that 35-40 min gap, were the disconnections from something like a timeout or were they from a reconnect message from Twitch? I'm asking because it could be you were just experiencing a period of poor connectivity. The latter case can be checked by looking in the DEBUG logs for a reconnect message just prior to the reconnection attempt. Otherwise, it'll be the prior case and you should see either the code and reason in the #[client number] - WebSocket closed: [status] - [reason] or the error in the #[client number] - WebSocket error: [error message] logs.

For reference, the reconnect message logs look like:

[timestamp] - DEBUG - TwitchChannelPointsMiner.classes.websocket.hermes.Client - [on_message]: #0 - Received: {"reconnect":{"url":"wss://hermes.twitch.tv/b/v1?..."},"id":"...","type":"reconnect","timestamp":"..."}

where the type is reconnect.

@Gamerns10s
Copy link

I am on normal logging level as on debug the whole console gets flooded and it really hard to observe things but still I have changed the logging to debug only for websockets module (in the Twitchpointsminer.py file) and am still waiting for a disconnect to occur.
For that 35-40 min gap one it only logged connecting to hermes server in 30 secs and the None:None error (in INFO logging level)

Also while going through logs rn I found these too and fyi they didn't occurred during or near any reconnection/disconnection
IMG_20251001_152557

@mpforce1
Copy link
Author

mpforce1 commented Oct 1, 2025

I am on normal logging level as on debug the whole console gets flooded and it really hard to observe things but still I have changed the logging to debug only for websockets module (in the Twitchpointsminer.py file) and am still waiting for a disconnect to occur. For that 35-40 min gap one it only logged connecting to hermes server in 30 secs and the None:None error (in INFO logging level)

You can set the file logs to DEBUG while keeping the console logs to INFO, that's how I've got mine set up. You might already have it that way, in which case you can just check the log file.

Also while going through logs rn I found these too and fyi they didn't occurred during or near any reconnection/disconnection !

Looks like an unrelated error, a quick issue search doesn't find that anywhere so if it happens again while using the regular miner release you should open an issue about it.

@Gamerns10s
Copy link

I don't use that too cuz my vps doesn't have root access 🤦🏻‍♂️😂

@Gamerns10s
Copy link

AI analysis of what could be happening
It might be useful to spot the issue so thought to share
IMG_20251001_203050

@mpforce1
Copy link
Author

mpforce1 commented Oct 1, 2025

AI analysis of what could be happening It might be useful to spot the issue so thought to share

I've actually already considered, at least the accurate parts, of this and I'm not sure it's necessary to respect the keepalive timeout. It feels unnecessary to close the connection early when keepalives aren't being sent in a timely manner when it could just be server congestion. Saying this, I will give you that the Twitch Web client does seem to implement something like this so maybe it would be for the best.

The bit about "initiate the keep-alive" cycle is wrong, the server is responsible for sending keepalives. All the client has to do is connect, authenticate, and subscribe. The old PubSub API used a PING/PONG approach to keep the WebSocket connection alive but Hermes does not expect the client to send regular messages.

The more frequent disconnections might just be how this API is going to be. As long as the miner is able to reconnect, does it really matter if it's disconnecting more often? I might try and hide some of the log messages if the client is told by Twitch to reconnect, it's not a case the user should worry about.

@Gamerns10s
Copy link

Gamerns10s commented Oct 1, 2025

Logs from Debug websocket logging
It's always this error after which it logs that it disconnected
IMG_20251002_000641

@mpforce1
Copy link
Author

mpforce1 commented Oct 1, 2025

That's a weird one, never seen it log the frame as an error before. From what I can tell fin=1 means this is the final frame, opcode=8 means this is a close frame, and data=b'\x10\x04ping pong failed' is the data for the frame. The first 2 characters are actually the WebSocket status code and are calculated like 256 * int(self.data[0]) + int(self.data[1]) in the library we use. 0x10 is 16 and 0x04 is 4 so that's 256 * 16 + 4 = 4100 which is a custom code so we don't know exactly what that means to Twitch. You can see in the Twitch Web Hermes client that they specifically handle 4100 by (I think) logging it and resetting the socket. I definitely need the full logs files for this one since it's so strange.

Also, can you say where NoneType: None coming from?

@Gamerns10s
Copy link

As of now these are the only logs related to disconnections but I'll switch to debug logging and will get more info about the same

@Gamerns10s
Copy link

As of now this is all I could gather hope it helps
IMG_20251002_144220
IMG_20251002_144526

@mpforce1
Copy link
Author

mpforce1 commented Oct 2, 2025

@Gamerns10s I'm sorry but without the full stack trace for the error I can't pinpoint where it's coming from. Is there any chance you could run it on a machine where you have access to the log files? I would also need less=False in the logger settings.

@Gamerns10s
Copy link

In that case I can run it on termux but it'll take some time to get the logs but I'll surely try to share it asap

@Gamerns10s
Copy link

@mpforce1 do you have any other social media or email address where I can share you the logs file
I don't wanna share it publically here 😅

@mpforce1
Copy link
Author

mpforce1 commented Oct 2, 2025

@mpforce1 do you have any other social media or email address where I can share you the logs file I don't wanna share it publically here 😅

No, sorry. The last time this happened the user uploaded them to proton storage and posted a link here. Once I'd confirmed download they deleted the files. You could try something like that.

@Gamerns10s
Copy link

I think at one point my device lost internet connection you can ignore that disconnection 😅
https://limewire.com/d/GT4VA#YmcVtKLf43

@mpforce1
Copy link
Author

mpforce1 commented Oct 2, 2025

@Gamerns10s Got it. I'll let you know if I find anything.

@Gamerns10s
Copy link

IMG_20251002_222208
Here is a more precise one at 21:30:13 secs
https://uploadnow.io/f/xRfgv4v

@mpforce1
Copy link
Author

mpforce1 commented Oct 9, 2025

I encountered a new error 4100 once sadly don't have the log file for it rn

That's actually the same error as before, the ping pong failed one. It's just correctly coming through in on_close now since you're on the right release of websocket-client. No idea what causes a 4100 close status, but as long as the miner reconnects and continues working I wouldn't worry about it.

Encountered this error also many times but not sure if it was because of websocket or not cuz I am working on irc also rn Exception raised: file descriptor cannot be a negative integer (-1). Thread is active: True

I think I've seen somebody else complain about that one, might be worth doing a search. That message format Exception raised: [message]. Thread is active [state] is from the Chat.py file. So yeah, probably an irc thing.

@type2diabetes
Copy link

Been pretty solid since that last update, no issues on this end.

@Gamerns10s
Copy link

Ahh just wanted to tell you that the error update client version has become very frequent in long run sessions (24+ hours) and some more logs you should take a look at
IMG_20251012_212056
IMG_20251012_212219
IMG_20251012_212803
IMG_20251012_213156
IMG_20251012_213502
IMG_20251012_213747
IMG_20251012_213848
I just pray it's not again my websocket module error 😅🤦🏻‍♂️

@mpforce1
Copy link
Author

@Gamerns10s It's hard to tell what's going on because of how you've formatted the logs. To get a better idea I'd need text from the log file or an upload of the log file.

However, from what I can see it looks like you might have a poor connection between you and the Twitch servers. errno 32 and errno 104 could be caused by a timeout, afaik it's not really possible to tell since we'd need the Twitch server logs. The issue with update_client_version happening around the same time could also be the result of a poor connection. All that's doing is requesting the main Twitch site and parsing the page for the client version, the fact it fails suggests just a temporary bad connection. I sometimes see that error and it's usually fine.

I still have no idea what the 4100 ping pong failed issue is about. It almost sounds like the websocket-client library isn't sending pong frames correctly but you've have to enable trace logging (as per #728 (comment)), wait for the issue to occur, and send me the log file so I can see if it's receiving/sending them. It's a bit of a hassle since it clogs up the log files and makes it hard to even see the issue happening, but it's the only way to see the ping/pong frames.

@Gamerns10s
Copy link

Yeah I can totally understand that and will try to share the logs file soon
Also an unrelated question I am trying to make a drop fetching script but on sending gql request to the server I get persistent query error (I think I also needed to get an integrity token in some way and send it in the payload but I can't figure it out you got any idea about that? )

@mpforce1
Copy link
Author

Also an unrelated question I am trying to make a drop fetching script but on sending gql request to the server I get persistent query error (I think I also needed to get an integrity token in some way and send it in the payload but I can't figure it out you got any idea about that? )

Sorry, I don't know much about the drops system. I'd like to keep this pr more on topic, please start a separate discussion/issue if you want help with something else.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a new integration with Twitch's Hermes WebSocket API as a replacement for the legacy PubSub WebSocket API. The implementation addresses issue #722 regarding Twitch's completion of A/B testing for the new Hermes API.

Key changes:

  • Added a new Hermes WebSocket client and pool implementation with authentication and subscription management
  • Abstracted PubSub message handling logic into a shared PubSubHandler class to avoid code duplication
  • Introduced a new use_hermes configuration option (defaults to False for backward compatibility)

Reviewed Changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 32 comments.

Show a summary per file
File Description
example.py Adds the use_hermes configuration parameter to the example
README.md Documents the new use_hermes configuration option
TwitchChannelPointsMiner/utils.py Adds utility functions for random ID generation, timestamp formatting, and iterable combination used by Hermes implementation
TwitchChannelPointsMiner/constants.py Adds HERMES_WEBSOCKET constant and CLIENT_ID_WEB for browser client identification
TwitchChannelPointsMiner/classes/Settings.py Adds use_hermes to Settings slots for global configuration access
TwitchChannelPointsMiner/classes/PubSub.py Extracts message handling logic into PubSubHandler class for reuse across both WebSocket implementations
TwitchChannelPointsMiner/classes/entities/Message.py Adds get_topic() method to retrieve PubsubTopic from message
TwitchChannelPointsMiner/classes/websocket/Pool.py Defines abstract WebSocketPool base class for both implementations
TwitchChannelPointsMiner/classes/websocket/pubsub/Client.py Renames TwitchWebSocket to PubSubWebSocket and removes unused attributes
TwitchChannelPointsMiner/classes/websocket/pubsub/Pool.py Refactors legacy PubSub implementation to use shared message handler
TwitchChannelPointsMiner/classes/websocket/hermes/Client.py Implements HermesClient with state management, authentication, and subscription handling
TwitchChannelPointsMiner/classes/websocket/hermes/Pool.py Implements HermesWebSocketPool with client lifecycle management and reconnection logic
TwitchChannelPointsMiner/classes/websocket/hermes/data/*.py Adds request/response data models and JSON encoders/decoders for Hermes protocol
TwitchChannelPointsMiner/TwitchChannelPointsMiner.py Updates main class to conditionally create Hermes or PubSub pool based on configuration
TwitchChannelPointsMiner/classes/WebSocketsPool.py Removed legacy monolithic WebSocket pool implementation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

self.error = error
self.error_code = error_code

type Data = AuthenticateResponse.DataOk | AuthenticateResponse.DataError
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type statement for type aliases is only available in Python 3.12+, but this project supports Python 3.6+ according to setup.py. This will cause a SyntaxError on older Python versions. Consider using a regular assignment instead: Data = Union[AuthenticateResponse.DataOk, AuthenticateResponse.DataError] with from typing import Union.

Copilot uses AI. Check for mistakes.
"pubsub": self.pubsub.to_dict()
}

def __init__(self, subscribe: Data, _id: str | None = None, timestamp: datetime | None = None):
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union type syntax with | is only available in Python 3.10+, but this project supports Python 3.6+ according to setup.py. Use Optional[str] and Optional[datetime] from typing instead: def __init__(self, subscribe: Data, _id: Optional[str] = None, timestamp: Optional[datetime] = None).

Copilot uses AI. Check for mistakes.
self.__lock = threading.Lock()
"""Lock to prevent improper variable access in a multithreaded context."""

def topic(self, subscription_id: str) -> PubsubTopic | None:
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union type syntax with | is only available in Python 3.10+, but this project supports Python 3.6+ according to setup.py. Use Optional[PubsubTopic] from typing instead: def topic(self, subscription_id: str) -> Optional[PubsubTopic]:.

Copilot uses AI. Check for mistakes.
return None

def __create_new_client(
self, auth: TwitchLogin, topics: list[PubsubTopic], index: int | None = None
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generic list syntax list[...] is only available in Python 3.9+, but this project supports Python 3.6+ according to setup.py. Use List[PubsubTopic] from typing instead: from typing import List and topics: List[PubsubTopic].

Copilot uses AI. Check for mistakes.
def __repr__(self):
return simple_repr(self)

def __init__(self, _id: str | None = None, timestamp: datetime | None = None):
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The union type syntax with | is only available in Python 3.10+, but this project supports Python 3.6+ according to setup.py. Use Optional[str] and Optional[datetime] from typing instead: def __init__(self, _id: Optional[str] = None, timestamp: Optional[datetime] = None).

Copilot uses AI. Check for mistakes.
return _alphabet_base_36[value]
elif value < 62:
return _alphabet_base_36[value - 26].upper()
elif value > 62:
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic error in the mapping function: when value == 62, the function returns "-", but when value > 62 (which would be 63 since value is masked to 6 bits with value &= 63), it returns "_". This should be elif value == 62: instead of elif value > 62: since the only remaining case is exactly 62 after checking < 36 and < 62.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was just a direct translation of the js client code into python. It seems to work for me, when I run it locally it generates strings that contain _s and -s.

else:
logger.error(f"{client.describe()} - Exception raised for response: {response}", exc_info=e)
HermesClient.on_error(client, e)
except (json.JSONDecodeError, ValueError) as e:
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Inconsistent logging approach: When handling JSON decode errors (line 375), on_error is called, but when handling exceptions in message processing (line 374), on_error is also called. However, the JSON decode error already logs the error before calling on_error, while the message processing exception logs with exc_info. Consider standardizing the error logging approach.

Suggested change
except (json.JSONDecodeError, ValueError) as e:
except (json.JSONDecodeError, ValueError) as e:
logger.error(f"{client.describe()} - JSON decode error for message: {message}", exc_info=e)

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some merit to standardising the logging here. The reason it's like this is to mimic, as close as possible, the PubSub client's behaviour. However, looking at it, the "Exception raised for topiclog is in the newPubSub` handler so we can probably drop that. I'll look into that.

Comment on lines +219 to +227
# try:
# cookie_file_path = os.path.join("cookies", f"{username}.pkl")
# if os.path.exists(cookie_file_path):
# os.remove(cookie_file_path)
# logger.info(f"Deleted outdated cookie file for user: {username}")
# else:
# logger.warning(f"Cookie file not found for user: {username}")
# except Exception as e:
# logger.error(f"Error occurred while deleting cookie file: {str(e)}")
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment appears to contain commented-out code.

Suggested change
# try:
# cookie_file_path = os.path.join("cookies", f"{username}.pkl")
# if os.path.exists(cookie_file_path):
# os.remove(cookie_file_path)
# logger.info(f"Deleted outdated cookie file for user: {username}")
# else:
# logger.warning(f"Cookie file not found for user: {username}")
# except Exception as e:
# logger.error(f"Error occurred while deleting cookie file: {str(e)}")

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,3 @@
from .Pool import WebSocketPool
from .hermes import *
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import pollutes the enclosing namespace, as the imported module TwitchChannelPointsMiner.classes.websocket.hermes does not define 'all'.

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is actually a problem, I'm not familiar enough with python module exports.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is a problem because the .hermes import should only include things exported from the hermes/__init__.py file and that's just some specific imports from hermes/Client.py and hermes/Pool.py.

@@ -0,0 +1,3 @@
from .Pool import WebSocketPool
from .hermes import *
from .pubsub import *
Copy link

Copilot AI Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import pollutes the enclosing namespace, as the imported module TwitchChannelPointsMiner.classes.websocket.pubsub does not define 'all'.

Suggested change
from .pubsub import *
# TODO: Replace with explicit imports from pubsub, e.g.:
# from .pubsub import SomeClass, some_function

Copilot uses AI. Check for mistakes.
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

@mpforce1 mpforce1 force-pushed the hermes branch 2 times, most recently from 7bbcab5 to d9495f2 Compare November 15, 2025 15:01
@mpforce1 mpforce1 marked this pull request as ready for review November 15, 2025 15:04
@mpforce1
Copy link
Author

@rdavydov I've cleaned up the commit history and updated the minimum python version in the docs/dockerfile to 3.12. I feel it's ready to be merged so I've brought it out of draft.

Implements a WebSocket client and pool for the Hermes API.

Abstracts some of the handling for PubSub Messages.

Adds the "use_hermes" Setting to allow the use of the Hermes WebSocket API instead of the PubSub WebSocket API. Defaults to False.

Renamed WebSocket classes and moved them to new websocket module.
@brunoshure
Copy link

What's the state of this? I'm testing this currently and doesn't seem to be working very well. Granted this is still version 2.0.3 and not the latest 2.0.4.

It's running super slow and the channels don't get processed correctly. Lots of errors. Currently I'm only able to collect RAID points since the rest doesn't work.

@Gamerns10s
Copy link

For me it's working perfectly fine I just get a few connection reset by peer (maybe due to my server's network) sometimes and the drops aren't automatically being claimed like they are watched for the required time but aren't claimed from inventory until I restart the bot manually. Everything else is working flawlessly

@mpforce1
Copy link
Author

What's the state of this? I'm testing this currently and doesn't seem to be working very well. Granted this is still version 2.0.3 and not the latest 2.0.4.

It's running super slow and the channels don't get processed correctly. Lots of errors. Currently I'm only able to collect RAID points since the rest doesn't work.

@brunoshure

That's strange, it's been a bit since I used this branch since I've been working on other features so I can't say if there's been some change that's made it more unstable. However, when I last left it we seemed to be in a decent state and running it quickly now I don't see any issues.

Version 2.0.4 shouldn't really make a difference to this because that change is focused on fixing something in the watch loop not the WebSocket. I actually rebased onto 2.0.4 locally a while ago but never pushed up the change, I'll just do that now.

If you're running the miner on the same setup you've been testing my other branches (thanks a lot btw 😄) it's possible there's some sort of contamination between project files or python packages that's causing the issues you're seeing. Could you please give me some more context for your issues? When you say it's running slow do you mean load time? Can you show me an example of the errors you're seeing?

@brunoshure
Copy link

@mpforce1

My testing repos are stored in different folders and each one has its own venv. Dependencies are installed and script run while inside the venv. So I don't think there's any contamination.

It started spitting some errors right at the start and it took a while before actually showing something in the output. After that it only showed onlines, offlines and raids. No points were actually collected. And it stayed like that for the whole run.

Errors like these:

12/12/25 00:09:12 - ERROR - [post_gql_request]: Error with GQLOperations (GetIDFromLogin): HTTPSConnectionPool(host='gql.twitch.tv', port=443): Max retries exceeded with url: /gql (Caused by NameResolutionError("HTTPSConnection(host='gql.twitch.tv', port=443): Failed to resolve 'gql.twitch.tv' ([Errno -3] Temporary failure in name resolution)"))
12/12/25 00:56:10 - ERROR - [get_spade_url]: Something went wrong during extraction of 'spade_url': HTTPSConnectionPool(host='assets.twitch.tv', port=443): Max retries exceeded with url: /config/settings.6536def2f90a2ca65bc5b05820cd3464.js (Caused by NameResolutionError("HTTPSConnection(host='assets.twitch.tv', port=443): Failed to resolve 'assets.twitch.tv' ([Errno -3] Temporary failure in name resolution)"))
12/12/25 01:06:15 - ERROR - [post_gql_request]: Error with GQLOperations (VideoPlayerStreamInfoOverlayChannel): HTTPSConnectionPool(host='gql.twitch.tv', port=443): Max retries exceeded with url: /gql (Caused by NameResolutionError("HTTPSConnection(host='gql.twitch.tv', port=443): Failed to resolve 'gql.twitch.tv' ([Errno -3] Temporary failure in name resolution)"))
12/12/25 05:16:00 - ERROR - [on_error]: #46 - 99124b62-fa38-4b14-96a5-748e72970cb9 - WebSocket error: [Errno 104] Connection reset by peer
Traceback (most recent call last):
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_app.py", line 432, in initialize_socket
    dispatcher.read(self.sock.sock, read, check)
    ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_dispatcher.py", line 106, in read
    if not read_callback():
           ~~~~~~~~~~~~~^^
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_app.py", line 451, in read
    op_code, frame = self.sock.recv_data_frame(True)
                     ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_core.py", line 444, in recv_data_frame
    frame = self.recv_frame()
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_core.py", line 485, in recv_frame
    return self.frame_buffer.recv_frame()
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_abnf.py", line 375, in recv_frame
    self.recv_header()
    ~~~~~~~~~~~~~~~~^^
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_abnf.py", line 329, in recv_header
    header = self.recv_strict(2)
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_abnf.py", line 418, in recv_strict
    bytes_ = self.recv(min(16384, shortage))
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_core.py", line 581, in _recv
    return recv(self.sock, bufsize)
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_socket.py", line 127, in recv
    bytes_ = _recv()
  File "/home/bruno/shureandas-mpforce1-hermes/Twitch-Channel-Points-Miner-v2/venv/lib/python3.13/site-packages/websocket/_socket.py", line 99, in _recv
    return sock.recv(bufsize)
           ~~~~~~~~~^^^^^^^^^
  File "/usr/lib/python3.13/ssl.py", line 1285, in recv
    return self.read(buflen)
           ~~~~~~~~~^^^^^^^^
  File "/usr/lib/python3.13/ssl.py", line 1140, in read
    return self._sslobj.read(len)
           ~~~~~~~~~~~~~~~~~^^^^^

Logs:
https://temp.sh/xztjO/log.zip

@mpforce1
Copy link
Author

@brunoshure

My testing repos are stored in different folders and each one has its own venv. Dependencies are installed and script run while inside the venv. So I don't think there's any contamination.

That sounds perfect 😄

It started spitting some errors right at the start and it took a while before actually showing something in the output. After that it only showed onlines, offlines and raids. No points were actually collected. And it stayed like that for the whole run.

It took a while to output something because it took a few hours to load all of the data for the 1000+ streamers you were testing with.

12/12/25 00:09:12 - ERROR - [post_gql_request]: Error with GQLOperations (GetIDFromLogin): HTTPSConnectionPool(host='gql.twitch.tv', port=443): Max retries exceeded with url: /gql (Caused by NameResolutionError("HTTPSConnection(host='gql.twitch.tv', port=443): Failed to resolve 'gql.twitch.tv' ([Errno -3] Temporary failure in name resolution)"))
12/12/25 00:56:10 - ERROR - [get_spade_url]: Something went wrong during extraction of 'spade_url': HTTPSConnectionPool(host='assets.twitch.tv', port=443): Max retries exceeded with url: /config/settings.6536def2f90a2ca65bc5b05820cd3464.js (Caused by NameResolutionError("HTTPSConnection(host='assets.twitch.tv', port=443): Failed to resolve 'assets.twitch.tv' ([Errno -3] Temporary failure in name resolution)"))
12/12/25 01:06:15 - ERROR - [post_gql_request]: Error with GQLOperations (VideoPlayerStreamInfoOverlayChannel): HTTPSConnectionPool(host='gql.twitch.tv', port=443): Max retries exceeded with url: /gql (Caused by NameResolutionError("HTTPSConnection(host='gql.twitch.tv', port=443): Failed to resolve 'gql.twitch.tv' ([Errno -3] Temporary failure in name resolution)"))

These errors are unrelated to changes on this branch and suggest a temporary issue with your DNS.

12/12/25 05:16:00 - ERROR - [on_error]: #46 - 99124b62-fa38-4b14-96a5-748e72970cb9 - WebSocket error: [Errno 104] Connection reset by peer

This sort of error Connection reset by peer happens when Twitch drops your connection. This could easily be caused by whatever was causing the poor DNS performance you've already seen.

I can see in your logs that all requests to RequestBroadcastQualitiesURL return a 404 status which is strange, I'm not sure how it even managed to collect watch streaks to be honest. Perhaps watch streaks no longer require you to actually watch the stream? At any rate, it couldn't successfully send all minute watched event requests, which is why you didn't get any points while the miner was running. To the best of my knowledge, this has nothing to do with the changes to this branch.

As a follow up, I ran the miner with around 10 streamers all yesterday with no issues.

Do you get the same issues if you run the base miner with 1000+ streamers? Do you get the same problems if you run this branch with a much smaller amount of streamers, 10s rather than 100s?

@brunoshure
Copy link

@mpforce1

So I ran it again with your latest force-push and everything is working. No errors whatsoever, besides the occasional TypeError: 'NoneType' object is not subscriptable. I didn't modify the number of streamers.

I don't know what happened but 2.0.3 was not working at all.

For me it's working perfectly fine I just get a few connection reset by peer (maybe due to my server's network) sometimes and the drops aren't automatically being claimed like they are watched for the required time but aren't claimed from inventory until I restart the bot manually. Everything else is working flawlessly

It could be that he had made local changes but forgot about it, maybe that's why it was working fine for him. I cloned this branch fresh and did no changes. The only thing that I modified was the run.py file that needed the use_hermes=True thing.

I'm not sure how it even managed to collect watch streaks to be honest.

It didn't. It only collected RAID points.

@mpforce1
Copy link
Author

@brunoshure

So I ran it again with your latest force-push and everything is working. No errors whatsoever, besides the occasional TypeError: 'NoneType' object is not subscriptable. I didn't modify the number of streamers.
I don't know what happened but 2.0.3 was not working at all.

My best guess is a temporary network issue but it's hard to say.

It could be that he had made local changes but forgot about it, maybe that's why it was working fine for him. I cloned this branch fresh and did no changes. The only thing that I modified was the run.py file that needed the use_hermes=True thing.

I think they've been doing some work on a private fork, so yeah could be.

I'm not sure how it even managed to collect watch streaks to be honest.

It didn't. It only collected RAID points.

Oh my bad, that makes perfect sense then. Raids use a different endpoint so that one must not have been having that 404 issue.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Integrate With The Hermes API

8 participants