rustplus Python Library

repository·master·Indexed 41 days ago

https://github.com/olijeffers0n/rustplus

A Python library for interfacing with the Rust+ API to programmatically interact with Rust servers, teams, and smart devices. It provides the RustSocket class for managing connections and includes methods to retrieve server info, team chat, map markers, and entity data, as well as the ability to set entity values and retrieve monitor contents.

Tokens
11.2K
Snippets
48
Records
67
Agent score
51%

What's inside rustplus

  1. Overview of rustplus capabilities

    master

    The rustplus library provides programmatic access to Rust+ features. Supported capabilities include:

    • Map & Server Data: Get the Map, Get Server Info, and Getting the time.
    • Team & Clan Interaction: Get Team Info, Get Team Chat, Send Team Messages, and Interacting with clans.
    • Hardware & Environment: Using Smart Devices, Camera Handling, and Getting nexus information.
  2. How the Rust+ Event System works

    master

    The Rust+ library uses a decorator-based event system to listen for specific socket events. When an event occurs on the server, the corresponding decorated asynchronous function is called by the socket.

    There are four primary event types you can listen for:

    1. Chat Event: Triggered when a message is sent to team chat.
    2. Entity Event: Triggered when an entity (like an alarm or door) changes state.
    3. Team Event: Triggered when team information changes (e.g., players joining or leaving).
    4. Protobuf Event: Triggered when raw protobuf data is received over the websocket (intended for monitoring only).

    Important Requirement for Entity Events: You must call get_entity_info(eid) for a specific entity ID before an EntityEvent listener will receive any data. This action effectively "subscribes" your client to that entity.

    from rustplus import EntityEventPayload, TeamEventPayload, ChatEventPayload, ProtobufEvent, ChatEvent, EntityEvent, TeamEvent
    
    # Example of subscribing to an Entity Event
    @EntityEvent(server_details, 25743493)
    async def alarm(event: EntityEventPayload):
        value = "On" if event.value else "Off"
        print(f"Entity has been turned {value}")
    
    # Example of subscribing to a Team Event
    @TeamEvent(server_details)
    async def team(event: TeamEventPayload):
        print(f"The team leader's steamId is: {event.team_info.leader_steam_id}")
    
    # Example of subscribing to a Chat Event
    @ChatEvent(server_details)
    async def chat(event: ChatEventPayload):
        print(f"{event.message.name}: {event.message.message}")
    
    # Example of subscribing to a Protobuf Event
    @ProtobufEvent(server_details)
    async def proto(data: bytes):
        print(data)
  3. Handle errors with RustError

    master

    API methods in rustplus return an instance of RustError instead of raising exceptions when a call is unsuccessful. This design allows for easier error handling and reconnection logic. You can detect an error by checking if the returned object is an instance of RustError and then accessing its .reason attribute.

    time = await socket.get_time()
    if isinstance(time, RustError):
        print(f"Error Occurred, Reason: {time.reason}")
  4. Understand RustPlus server rate limits and token costs

    master

    The server enforces rate limits based on a Token Bucket approach. Limits are applied per IP address and per PlayerID:

    Limits

    • Per IP Address: 50 tokens max, replenished at 15 tokens/second.
    • Per PlayerID: 25 tokens max, replenished at 3 tokens/second.

    Token Costs per Operation

    OperationToken Cost
    get_map5 or 6 (depending on map content)
    get_time1
    get_info1
    get_markers1
    send_team_chat2
    get_team_info1
    turn_on_switch1
    turn_off_switch1
    get_entitiy_info1
    promote_to_team_leader1
    get_tc_storage_contents1
    get_current_events1
    (Un)Subscribing to/from a Camera1
    Moving a Camera0.01
  5. How registered listeners work

    master

    When you register a listener, it is wrapped in a RegisteredListener object. This wrapper allows you to manage the lifecycle of the listener, including removing it later. If you need to access the original coroutine from the wrapper, use the get_coro() method.

    # If you have a RegisteredListener object 'reg_listener'
    coroutine = reg_listener.get_coro()
  6. Keep a script running to listen for commands using hang()

    master

    When using commands in a script, the execution will naturally terminate once the script reaches its end. To prevent the script from exiting and allow it to continue listening for incoming commands, you must use the hang() method on your rust_socket instance.

    Warning: Calling await rust_socket.hang() is a non-recoverable operation. The script will remain stuck at this line indefinitely until the program is manually force-closed.

    await rust_socket.hang()
  7. Listen for FCM notifications with FCMListener

    master

    To listen for real-time messages from the Rust+ game server (such as pairing requests), extend the FCMListener class and override the on_notification method.

    1. Load your FCM configuration from the rustplus.py.config.json file (generated by the RustCli during registration).
    2. Create a subclass of FCMListener.
    3. Implement on_notification(self, obj, notification, data_message) to handle incoming messages.
    4. Instantiate your class with the configuration details and call .start() to begin listening.
    from rustplus import FCMListener
    import json
    
    # Load configuration generated by RustCli
    with open("rustplus.py.config.json", "r") as input_file:
        fcm_details = json.load(input_file)
    
    class FCM(FCMListener):
        def on_notification(self, obj, notification, data_message):
            # This method is called every time a message is received
            print(notification)
            
    FCM(fcm_details).start()
  8. Convert marker coordinates to Grid Reference

    master

    To convert raw x and y coordinates from a marker into a human-readable Grid Reference, use the convert_coordinates function. This requires the map_size obtained from the rust_socket.get_info() method.

    from rustplus import convert_coordinates
    
    info = await rust_socket.get_info()
    if isinstance(info, RustError):
        print(f"Error Occurred, Reason: {info.reason}")
    
    # Assuming x and y are retrieved from a RustMarker
    grid = convert_coordinates((x, y), info.map_size)
    print(f"Grid Reference: {grid}")
  9. Connect to a Rust server using RustSocket

    master

    To interact with a Rust server, you must first obtain your personal details (IP, Port, SteamID, and PlayerToken) using the required web tool. You then initialize a ServerDetails object and pass it to a RustSocket instance. Use await socket.connect() to establish the connection.

    import asyncio
    from rustplus import RustSocket, ServerDetails
    
    async def main():
        # Replace IP, PORT, STEAMID, and PLAYERTOKEN with your actual details
        server_details = ServerDetails("IP", "PORT", STEAMID, PLAYERTOKEN)
        socket = RustSocket(server_details)
        await socket.connect()
    
        # Example: Get server time
        print(f"It is {(await socket.get_time()).time}")
    
        await socket.disconnect()
    
    asyncio.run(main())
  10. Convert Unix timestamps to datetime

    master

    Many clan properties (like joined_time, last_seen, clan_time_created, motd_time_set, and invited_time) are returned as Unix timestamps. Use Python's datetime module to convert them into human-readable formats.

    from datetime import datetime
    
    # Assuming 'clan' is a RustClanInfo object
    created = datetime.fromtimestamp(clan.clan_time_created)
    print(created.strftime("%d %B %Y"))
  11. Control drones and cameras

    master

    Movement and actions can be sent to a camera using several methods. Before sending actions, it is recommended to verify capability using camera_manager.can_move(option).

    Important: You must call camera_manager.clear_movement() after performing movements. If you do not clear the movement, the server will treat the mouse as being continuously clicked or moving.

    # Check capabilities
    if camera_manager.can_move(CameraMovementOptions.FIRE) and \
            camera_manager.can_move(CameraMovementOptions.MOUSE):
        
        # Send specific actions
        await camera_manager.send_actions([MovementControls.FIRE_PRIMARY])
        
        # Send mouse movement only
        await camera_manager.send_mouse_movement(Vector(1, 1))
        
        # Send combined actions and mouse movement
        await camera_manager.send_combined_movement(
            [MovementControls.FIRE_PRIMARY], Vector(1, 1))
    
        await asyncio.sleep(1)
        
        # CRITICAL: Clear movement to prevent continuous input
        await camera_manager.clear_movement()