Skip to content

Conversation

@JulyCrab
Copy link

@JulyCrab JulyCrab commented Sep 7, 2025

Hi there! While I was developing a simple 2D game engine, I relied on this to create presences. However, I discovered that if Discord wasn't running, the game or app would crash. To resolve this, I implemented a method to detect when Discord isn't running, preventing the app from crashing and enabling a reconnection retry feature.

Here's a Usage Example

from pypresence import Presence
import time
import threading

class DiscordRPCManager:
    def __init__(self, client_id):
        self.client_id = client_id
        self.rpc = Presence(client_id)
        self.connected = False
        self.retry_thread = None
        self.stop_retry = False
        
    def connect_with_retry(self, max_attempts=None, retry_interval=30):
        """
        Attempt to connect to Discord with automatic retries.
        
        Args:
            max_attempts (int, optional): Maximum number of retry attempts. 
                                         None for infinite retries.
            retry_interval (int): Seconds to wait between retry attempts.
        
        Returns:
            bool: True if connected, False if max attempts reached.
        """
        attempts = 0
        
        while not self.stop_retry and (max_attempts is None or attempts < max_attempts):
            attempts += 1
            
            # Check if Discord might be available first
            if self.rpc.is_discord_available():
                print(f"Attempt {attempts}: Discord appears to be available, attempting connection...")
                
                # Try to establish connection
                if self.rpc.connect():
                    self.connected = True
                    print("Successfully connected to Discord Rich Presence!")
                    return True
                else:
                    print("Connection failed (Discord might be starting up)...")
            else:
                print(f"Attempt {attempts}: Discord not detected as available...")
            
            # Wait before retrying
            if not self.stop_retry and (max_attempts is None or attempts < max_attempts):
                print(f"Waiting {retry_interval} seconds before next attempt...")
                time.sleep(retry_interval)
        
        print("Maximum connection attempts reached or retry stopped.")
        return False
    
    def start_auto_reconnect(self, retry_interval=30):
        """
        Start a background thread that automatically attempts to reconnect.
        
        Args:
            retry_interval (int): Seconds to wait between reconnect attempts.
        """
        self.stop_retry = False
        self.retry_thread = threading.Thread(
            target=self._reconnect_loop, 
            args=(retry_interval,),
            daemon=True
        )
        self.retry_thread.start()
    
    def _reconnect_loop(self, retry_interval):
        """Background thread for automatic reconnection"""
        while not self.stop_retry:
            if not self.connected and self.rpc.is_discord_available():
                if self.rpc.connect():
                    self.connected = True
                    print("Reconnected to Discord Rich Presence!")
            
            time.sleep(retry_interval)
    
    def stop_auto_reconnect(self):
        """Stop the automatic reconnection thread"""
        self.stop_retry = True
        if self.retry_thread:
            self.retry_thread.join(timeout=1.0)
    
    def update_activity(self, **kwargs):
        """
        Safely update activity - returns True if successful, False otherwise.
        Will attempt to reconnect if not connected.
        """
        if not self.connected:
            # Try to reconnect if we're not connected
            if self.rpc.is_discord_available() and self.rpc.connect():
                self.connected = True
                print("Reconnected while updating activity!")
            else:
                return False
        
        # Now try to update
        result = self.rpc.update(**kwargs)
        if result is None:
            # Update failed - we might have lost connection
            self.connected = False
            print("Lost connection to Discord during update")
            return False
        
        return True
    
    def close(self):
        """Clean up resources"""
        self.stop_auto_reconnect()
        self.rpc.close()
        self.connected = False

# Example usage patterns:

# Pattern 1: Simple one-time connection with retries
rpc_manager = DiscordRPCManager("your_client_id_here")

# Try to connect with up to 5 attempts, waiting 10 seconds between each
connected = rpc_manager.connect_with_retry(max_attempts=5, retry_interval=10)
if connected:
    rpc_manager.update_activity(state="Playing an awesome game", details="In menu")
else:
    print("Could not connect to Discord - continuing without Rich Presence")

# Pattern 2: Background auto-reconnect (recommended for long-running apps)
rpc_manager = DiscordRPCManager("your_client_id_here")

# Start background thread that checks every 30 seconds for Discord
rpc_manager.start_auto_reconnect(retry_interval=30)

# Try to use Rich Presence - it will automatically connect when available
if rpc_manager.update_activity(state="Playing game", details="Level 5"):
    print("Activity updated successfully!")
else:
    print("Not connected yet - activity will update when Discord is available")

# Later, when your app is closing:
rpc_manager.close()

# Pattern 3: Simple inline retry logic
rpc = Presence("your_client_id_here")
connected = False
attempts = 0

while not connected and attempts < 3:
    attempts += 1
    if rpc.is_discord_available():
        connected = rpc.connect()
        if connected:
            rpc.update(state="Game running", details="Having fun!")
        else:
            print(f"Connection attempt {attempts} failed")
            time.sleep(15)
    else:
        print("Discord not detected")
        time.sleep(15)

if not connected:
    print("Using fallback mode without Discord integration")

@kaneryu
Copy link
Collaborator

kaneryu commented Oct 15, 2025

Great idea. Looking into this for 4.7.0

@kaneryu kaneryu added the enhancement New feature or request label Oct 15, 2025
@kaneryu kaneryu added this to the 4.7.0 milestone Oct 15, 2025
@JulyCrab
Copy link
Author

Great idea. Looking into this for 4.7.0

Awesome to know ^^

Let me know once it gets merged!

@kaneryu kaneryu self-assigned this Oct 19, 2025
@kaneryu kaneryu requested a review from Copilot October 19, 2025 11:48
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 adds functionality to prevent crashes when Discord is not running by implementing connection state checking and graceful error handling. The main purpose is to allow applications to continue running without Discord Rich Presence when Discord is unavailable, with optional retry mechanisms.

  • Added connection state checking before operations to prevent crashes
  • Implemented is_discord_available() method to detect Discord availability
  • Modified connection methods to return boolean success indicators instead of raising exceptions

Reviewed Changes

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

File Description
pypresence/presence.py Added connection state checks, error handling, and modified method signatures to remove type annotations
pypresence/baseclient.py Added Discord availability detection, improved connection handling, and enhanced error handling with cleanup

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

JulyCrab and others added 8 commits October 26, 2025 11:50
Added type annotations to BaseClient methods and attributes for better code clarity and static analysis. Improved error handling by catching specific exceptions and logging debug messages during connection checks and cleanup. Minor refactoring for consistency and maintainability.
Added type annotations to methods in Presence and AioPresence classes for improved code clarity and static analysis. Introduced logging for exception handling to aid in debugging, replacing silent exception handling with debug log messages.
@JulyCrab
Copy link
Author

Great idea. Looking into this for 4.7.0

I've updated my files according to the review. It should be good to go now ^^

Replaces ConnectionError with PyPresenceException when not connected to Discord and removes the message from InvalidPipe exception for consistency.
Enhanced the Presence.close method to handle cases where the socket writer is not present, ensuring proper cleanup of resources even on error. Also refactored the buttons example for cleaner imports and more concise code formatting.
Simplifies type annotations and exception handling in BaseClient and Presence classes. Removes unnecessary imports, improves error logging, and ensures consistent cleanup of resources. Adds more robust handling for connection errors and unexpected exceptions, and streamlines method signatures for clarity.
@kaneryu
Copy link
Collaborator

kaneryu commented Oct 28, 2025

This is a breaking change, and will thus be pushed back until 5.x.x. Sorry for the delay in a manual review, I'll get to it sometime this week.

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

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants