1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
//! This file contains a system for signaling Discord RPC context changes between threads.
//!
//! ## Description
//!
//! The idea is that the thread containing the Discord RPC client can hold a `StatefulDiscordRpcSignalHandler` as a stateful context.
//! The game thread can then send `DiscordRpcSignal` values through an `mpsc` sender, which will be received by the Discord RPC client thread.

use chrono::Utc;
use discord_sdk::activity::{ActivityBuilder, Assets};

/// Definitions of signals that can be sent to the Discord RPC thread to control how discord displays game status.
#[derive(Debug, Clone)]
pub enum DiscordRpcSignal {
    /// Signal to begin a game timer (Discord will display `XX:XX elapsed`)
    BeginGameTimer,

    /// Signal to end a game timer
    EndGameTimer,

    /// Signal to begin a countdown timer (Discord will display `XX:XX left`)
    SetGameTimeRemainingTimestamp(chrono::DateTime<Utc>),

    /// Signal to clear the game remaining timer
    ClearGameTimeRemaining,

    /// Signal to set the details in the info card
    ChangeDetails {
        /// What the player is doing, eg. “Exploring the Wilds of Outland”.
        ///
        /// Limited to 128 bytes.
        details: String,

        /// The user’s currenty party status, eg. “Playing Solo”.
        ///
        /// Limited to 128 bytes.
        party_status: Option<String>,
    },

    /// Signal to change the graphical assets in the info card
    ChangeAssets(Assets),
}

/// A struct that can keep track of incoming signals and their effect on Discord
#[derive(Default, Debug, Clone)]
pub struct StatefulDiscordRpcSignalHandler {
    game_start_timer: Option<chrono::DateTime<Utc>>,
    game_end_timer: Option<chrono::DateTime<Utc>>,
    game_details: Option<String>,
    game_party_status: Option<String>,
    game_assets: Option<Assets>,
}

impl StatefulDiscordRpcSignalHandler {
    /// Apply a signal to generate a new activity
    pub fn apply(&mut self, signal: DiscordRpcSignal) -> ActivityBuilder {
        // Fill in the data based on the contents of the signal
        match signal {
            DiscordRpcSignal::BeginGameTimer => self.game_start_timer = Some(chrono::Utc::now()),
            DiscordRpcSignal::EndGameTimer => self.game_start_timer = None,
            DiscordRpcSignal::SetGameTimeRemainingTimestamp(timestamp) => {
                self.game_end_timer = Some(timestamp)
            }
            DiscordRpcSignal::ClearGameTimeRemaining => self.game_end_timer = None,
            DiscordRpcSignal::ChangeDetails {
                details,
                party_status,
            } => {
                self.game_details = Some(details);
                self.game_party_status = party_status;
            }
            DiscordRpcSignal::ChangeAssets(assets) => self.game_assets = Some(assets),
        }

        // Decide how to build the Discord RPC activity
        let mut builder = ActivityBuilder::default();
        if let Some(start_time) = &self.game_start_timer {
            builder = builder.start_timestamp(start_time.timestamp());
        }
        if let Some(end_time) = &self.game_end_timer {
            builder = builder.end_timestamp(end_time.timestamp());
        }
        if let Some(details) = &self.game_details {
            builder = builder.details(details);
        }
        if let Some(party_status) = &self.game_party_status {
            builder = builder.state(party_status);
        }
        if let Some(assets) = &self.game_assets {
            builder = builder.assets(assets.clone());
        }

        return builder;
    }
}