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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// Comprehensive example demonstrating ALL ActivityBuilder options
//
// This example shows every available builder method with explanations
// of what each field does and how it appears in Discord.
use clap::Parser;
use presenceforge::sync::DiscordIpcClient;
use presenceforge::{ActivityBuilder, Result};
use std::time::Duration;
/// Discord Rich Presence Complete Builder Example
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
/// Discord Application Client ID
#[arg(short, long)]
client_id: Option<String>,
}
fn main() -> Result {
// Load .env file if it exists (optional)
let _ = dotenvy::dotenv();
let args = Args::parse();
let client_id = args
.client_id
.or_else(|| std::env::var("DISCORD_CLIENT_ID").ok())
.unwrap_or_else(|| {
eprintln!("Error: DISCORD_CLIENT_ID is required!");
eprintln!("Provide it via:");
eprintln!(" - Command line: cargo run --example builder_all -- --client-id YOUR_ID");
eprintln!(" - Environment: DISCORD_CLIENT_ID=YOUR_ID cargo run --example builder_all");
eprintln!(" - .env file: Create .env from .env.example and set DISCORD_CLIENT_ID");
std::process::exit(1);
});
println!("=== Complete ActivityBuilder Reference Example ===\n");
let mut client = DiscordIpcClient::new(client_id)?;
// Perform handshake
println!("Connecting to Discord...");
client.connect()?;
println!("✓ Connected!\n");
// Create an activity with ALL possible fields
println!("Setting activity with all available options...\n");
let mut builder = ActivityBuilder::new()
// ═══════════════════════════════════════════════════════════
// BASIC TEXT FIELDS
// ═══════════════════════════════════════════════════════════
// State: First line of text (smaller text)
// Example: "In a Match" or "Editing main.rs"
.state("Playing a custom game")
// Details: Second line of text (larger text above state)
// Example: "Competitive - Rank 50" or "Workspace: my-project"
.details("Custom game mode with friends")
// ═══════════════════════════════════════════════════════════
// TIMESTAMPS (Shows elapsed/remaining time)
// ═══════════════════════════════════════════════════════════
// Start timestamp: Shows "elapsed" time (e.g., "00:15 elapsed")
// Use .start_timestamp_now() for current time
// End timestamp: Shows "remaining" time (e.g., "02:30 left")
// Note: If you set both start and end, Discord shows remaining time
// Uncomment to try:
// .end_timestamp(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + 300)
// ═══════════════════════════════════════════════════════════
// IMAGES (Large and Small)
// ═══════════════════════════════════════════════════════════
// Large Image: Main big image shown on the left
// This should be an asset key uploaded to your Discord app
// Go to: Discord Developer Portal > Your App > Rich Presence > Art Assets
.large_image("car")
// Large Image Text: Tooltip shown when hovering over large image
.large_text("This is the large image - shows on hover!")
// Small Image: Smaller circular image shown in bottom-right of large image
// Also needs to be uploaded as an asset
.small_image("rust_logo")
// Small Image Text: Tooltip shown when hovering over small image
.small_text("Built with Rust 🦀")
// ═══════════════════════════════════════════════════════════
// PARTY (Multiplayer/Group information)
// ═══════════════════════════════════════════════════════════
// Party: Shows "X of Y" (e.g., "2 of 4" for a party)
// Useful for multiplayer games showing current players
// Parameters: party_id, current_size, max_size
.party("party-12345", 2, 4)
// NOTE : BUTTONS and SECRETS are mutually exclusive. We add buttons
// only when the `secrets` feature is NOT enabled so the example
// remains valid in both configurations.
// ═══════════════════════════════════════════════════════════
// SECRETS (For "Ask to Join" and spectate features)
// ═══════════════════════════════════════════════════════════
// NOTE: Secret methods require the 'secrets' feature flag.
// To enable, run the example with `--features secrets`.
// The secret methods are only compiled when the feature is enabled,
// so we call them inside a cfg block below.
// ═══════════════════════════════════════════════════════════
// INSTANCE (Boolean flag)
// ═══════════════════════════════════════════════════════════
// Instance: Whether this is an instanced context (like a match)
// Set to true for unique game instances, false for general activities
.instance(true);
// Apply the start timestamp (this can fail if system time is invalid)
builder = builder.start_timestamp_now()?;
// Conditionally add buttons when the secrets feature is NOT enabled
#[cfg(not(feature = "secrets"))]
{
builder = builder
.button("View Game", "https://example.com/game")
.button("Documentation", "https://docs.rs/presenceforge");
}
// Conditionally add secrets when feature is enabled
#[cfg(feature = "secrets")]
{
builder = builder
.join_secret("join_secret_12345")
.spectate_secret("spectate_secret_67890")
.match_secret("match_secret_abcde");
}
// Build the activity
let activity = builder.build();
// Set the activity
// println!("{activity:?}");
client.set_activity(&activity)?;
println!("✓ Activity set successfully!");
println!("\n📱 Check your Discord profile to see the activity!");
println!(" You should see:");
println!(" • Details: 'Custom game mode with friends'");
println!(" • State: 'Playing a custom game'");
println!(" • Large image with tooltip");
println!(" • Small image (Rust logo) in corner");
println!(" • Party info: '2 of 4'");
#[cfg(not(feature = "secrets"))]
println!(" • Two clickable buttons");
#[cfg(feature = "secrets")]
println!(" • Join/spectate/match secrets enabled (ask-to-join)");
println!(" • Elapsed time counter");
// Keep activity visible for 30 seconds
println!("\nKeeping activity visible for 30 seconds...");
for i in 1..=30 {
print!("\r {} seconds remaining... ", 31 - i);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
std::thread::sleep(Duration::from_secs(1));
}
println!("\r ✓ Time's up! ");
// Clear the activity
println!("\nClearing activity...");
client.clear_activity()?;
println!("✓ Activity cleared!");
println!("\n=== Example completed! ===\n");
// ═══════════════════════════════════════════════════════════
// VISUAL REFERENCE
// ═══════════════════════════════════════════════════════════
println!("📊 Visual Layout Reference:");
println!("┌─────────────────────────────────────────┐");
println!("│ [Large Image] DETAILS │");
println!("│ [Small] State │");
println!("│ Party: 2 of 4 │");
println!("│ ⏱ 00:15 elapsed │");
println!("│ │");
#[cfg(not(feature = "secrets"))]
println!("│ [Button 1] [Button 2] │");
#[cfg(feature = "secrets")]
println!("│ [Secrets] │");
println!("└─────────────────────────────────────────┘");
println!();
println!("💡 Tips:");
println!(" • Large/Small images must be uploaded as assets in Discord Developer Portal");
println!(" • Asset keys are case-sensitive");
println!(" • Maximum 2 buttons allowed");
println!(" • Timestamps are Unix timestamps (seconds since epoch)");
println!(" • Party size shows as 'X of Y' in Discord");
println!(" • Secrets (join/spectate/match) require the 'secrets' feature flag");
println!(" • Hover tooltips work on large_text and small_text");
Ok(())
}