steam_client/services/
rich_presence.rs1use std::collections::HashMap;
8
9use steam_enums::EMsg;
10use steamid::SteamID;
11
12use crate::{error::SteamError, SteamClient};
13
14#[derive(Debug, Clone, Default)]
16pub struct RichPresenceData {
17 pub steam_id: SteamID,
19 pub appid: u32,
21 pub data: HashMap<String, String>,
23}
24
25impl SteamClient {
26 pub async fn upload_rich_presence(&mut self, appid: u32, data: &HashMap<String, String>) -> Result<(), SteamError> {
46 if !self.is_logged_in() {
47 return Err(SteamError::NotLoggedOn);
48 }
49
50 let kv_bytes = encode_rich_presence_kv(data);
52
53 self.session_recovery.record_rich_presence(appid, data.clone());
55
56 let msg = steam_protos::CMsgClientRichPresenceUpload { rich_presence_kv: Some(kv_bytes), ..Default::default() };
57
58 self.send_message_with_routing(EMsg::ClientRichPresenceUpload, appid, &msg).await
60 }
61
62 pub async fn request_rich_presence(&mut self, appid: u32, steam_ids: &[SteamID]) -> Result<(), SteamError> {
75 if !self.is_logged_in() {
76 return Err(SteamError::NotLoggedOn);
77 }
78
79 if steam_ids.is_empty() {
80 return Ok(());
81 }
82
83 let msg = steam_protos::CMsgClientRichPresenceRequest { steamid_request: steam_ids.iter().map(|sid| sid.steam_id64()).collect() };
84
85 self.send_message_with_routing(EMsg::ClientRichPresenceRequest, appid, &msg).await
86 }
87
88 pub async fn get_app_rich_presence_localization(&mut self, appid: i32, language: &str) -> Result<(), SteamError> {
94 if !self.is_logged_in() {
95 return Err(SteamError::NotLoggedOn);
96 }
97
98 let request = steam_protos::CCommunityGetAppRichPresenceLocalizationRequest { appid: Some(appid), language: Some(language.to_string()) };
99
100 self.send_service_method("Community.GetAppRichPresenceLocalization#1", &request).await
101 }
102
103 pub async fn clear_rich_presence(&mut self, appid: u32) -> Result<(), SteamError> {
107 self.upload_rich_presence(appid, &HashMap::new()).await
108 }
109}
110
111fn encode_rich_presence_kv(data: &HashMap<String, String>) -> Vec<u8> {
123 let mut buf = Vec::with_capacity(1024);
124
125 buf.push(0x00);
127
128 buf.extend_from_slice(b"RP\0");
130
131 for (key, value) in data {
133 buf.push(0x01); buf.extend_from_slice(key.as_bytes());
135 buf.push(0x00); buf.extend_from_slice(value.as_bytes());
137 buf.push(0x00); }
139
140 buf.push(0x08);
142 buf.push(0x08);
143
144 buf
145}
146
147pub fn parse_rich_presence_kv(data: &[u8]) -> HashMap<String, String> {
149 let mut result = HashMap::new();
150
151 if data.is_empty() {
152 return result;
153 }
154
155 let mut i = 0;
156
157 if i < data.len() && data[i] == 0x00 {
159 i += 1;
160 }
161
162 while i < data.len() && data[i] != 0x00 {
164 i += 1;
165 }
166 if i < data.len() {
167 i += 1; }
169
170 while i < data.len() {
172 let type_byte = data[i];
173 i += 1;
174
175 if type_byte == 0x08 {
176 break;
178 }
179
180 if type_byte != 0x01 {
181 continue;
183 }
184
185 let key_start = i;
187 while i < data.len() && data[i] != 0x00 {
188 i += 1;
189 }
190 let key = String::from_utf8_lossy(&data[key_start..i]).to_string();
191 if i < data.len() {
192 i += 1; }
194
195 let value_start = i;
197 while i < data.len() && data[i] != 0x00 {
198 i += 1;
199 }
200 let value = String::from_utf8_lossy(&data[value_start..i]).to_string();
201 if i < data.len() {
202 i += 1; }
204
205 result.insert(key, value);
206 }
207
208 result
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_encode_decode_rich_presence() {
217 let mut data = HashMap::new();
218 data.insert("status".to_string(), "In Menu".to_string());
219 data.insert("connect".to_string(), "+connect 1.2.3.4:27015".to_string());
220
221 let encoded = encode_rich_presence_kv(&data);
222 let decoded = parse_rich_presence_kv(&encoded);
223
224 assert_eq!(decoded.get("status"), Some(&"In Menu".to_string()));
225 assert_eq!(decoded.get("connect"), Some(&"+connect 1.2.3.4:27015".to_string()));
226 }
227
228 #[test]
229 fn test_empty_rich_presence() {
230 let data = HashMap::new();
231 let encoded = encode_rich_presence_kv(&data);
232 let decoded = parse_rich_presence_kv(&encoded);
233
234 assert!(decoded.is_empty());
235 }
236}