1use std::collections::HashMap;
2
3use steamid::SteamID;
4
5#[cfg(feature = "gas")]
6use crate::gas::GasSteamUser;
7#[cfg(feature = "remote")]
8use crate::remote::RemoteSteamUser;
9use crate::{
10 action::{ActionContext, ApiAction},
11 client::SteamUser,
12 error::SteamUserError,
13 steam_user_api::SteamUserApi,
14 types::{
15 AccountDetails, ActiveInventory, ActivityCommentResponse, AddPhoneNumberResponse, AliasEntry, Amount, AppDetail, AppId, AppListItem, AssetId, AvatarHistoryEntry, AvatarUploadResponse, BoosterPackEntry, BoosterResult, CommunitySearchResult, ConfirmPhoneCodeResponse, Confirmation, ContextId, CsgoAccountStats, DynamicStoreUserData, EconItem, FriendActivity, FriendActivityResponse, FriendDetails, FriendListPage, GemResult, GemValue, GroupInfoXml, GroupOverview, HelpRequest, InventoryHistoryItem, InventoryHistoryResult, InvitableGroup, ItemNameId, ItemOrdersHistogramResponse, LoggedInResult, MarketHistoryResponse, MarketRestrictions,
16 MatchHistoryResponse, MyListingsResult, Notifications, OwnedApp, OwnedAppDetail, PendingFriendList, PlayerReport, PriceCents, PriceOverview, PrivacySettings, PurchaseHistoryItem, RedeemWalletCodeResponse, RemovePhoneResult, SellItemResult, SimpleSteamAppList, SteamAppVersionInfo, SteamGuardStatus, SteamProfile, SteamUserProfile, TradeOfferAsset, TradeOfferResult, TradeOffersResponse, TwoFactorResponse, UserComment, UserSummaryProfile, UserSummaryXml, WalletBalance,
17 },
18};
19
20pub struct FallbackSteamUser {
28 pub local: SteamUser,
29 #[cfg(feature = "remote")]
30 pub remote: Option<RemoteSteamUser>,
31 #[cfg(feature = "gas")]
32 pub gas: Option<GasSteamUser>,
33}
34
35impl FallbackSteamUser {
36 pub fn new(local: SteamUser) -> Self {
37 Self {
38 local,
39 #[cfg(feature = "remote")]
40 remote: None,
41 #[cfg(feature = "gas")]
42 gas: None,
43 }
44 }
45
46 #[cfg(feature = "remote")]
47 pub fn with_remote(mut self, remote: RemoteSteamUser) -> Self {
48 self.remote = Some(remote);
49 self
50 }
51
52 #[cfg(feature = "gas")]
53 pub fn with_gas(mut self, gas: GasSteamUser) -> Self {
54 self.gas = Some(gas);
55 self
56 }
57}
58
59macro_rules! fallback_methods {
60 (@record_arg identity_secret, $val:expr) => { "[REDACTED]".to_string() };
62 (@record_arg shared_secret, $val:expr) => { "[REDACTED]".to_string() };
63 (@record_arg pin, $val:expr) => { "[REDACTED]".to_string() };
64 (@record_arg code, $val:expr) => { "[REDACTED]".to_string() };
65 (@record_arg activation_code, $val:expr) => { "[REDACTED]".to_string() };
66 (@record_arg revocation_code, $val:expr) => { "[REDACTED]".to_string() };
67 (@record_arg api_key, $val:expr) => { "[REDACTED]".to_string() };
68 (@record_arg $name:ident, $val:expr) => { format!("{:?}", $val) };
69
70 ($( $method:ident( $($arg:ident : $argty:ty),* ) -> $ret:ty => $action:ident; )*) => {
71 #[allow(deprecated)]
72 #[async_trait::async_trait]
73 impl SteamUserApi for FallbackSteamUser {
74 type Error = SteamUserError;
75
76 $(
77 #[allow(unused_assignments)]
78 #[tracing::instrument(target = "steam_user", skip_all, fields(http_method, url, raw_response, response_type, content_type))]
79 async fn $method(&self $(, $arg: $argty)*) -> Result<$ret, Self::Error> {
80 let _start = std::time::Instant::now();
81 let _steam_id = self.local.steam_id().and_then(|s| i64::try_from(s.steam_id64()).ok()).unwrap_or(0);
82 let _input = if tracing::enabled!(target: "steam_user", tracing::Level::WARN) {
83 #[allow(unused_mut)]
84 let mut parts: Vec<String> = Vec::new();
85 $(
86 parts.push(format!("{}: {}", stringify!($arg), fallback_methods!(@record_arg $arg, &$arg)));
87 )*
88 if parts.is_empty() {
89 String::new()
90 } else {
91 format!("{{ {} }}", parts.join(", "))
92 }
93 } else {
94 String::new()
95 };
96
97 let mut _source = "local";
98
99 #[allow(unused_mut)]
100 let mut last_error = match SteamUserApi::$method(&self.local, $($arg.clone()),*).await {
101 Ok(v) => {
102 let duration = i64::try_from(_start.elapsed().as_millis()).unwrap_or(i64::MAX);
103 let output_str = if tracing::enabled!(target: "steam_user", tracing::Level::DEBUG) {
104 let s = format!("{:?}", &v);
105 if s.len() > 256 { format!("{}…[truncated, {} bytes]", &s[..256], s.len()) } else { s }
106 } else {
107 String::new()
108 };
109 tracing::info!(
110 target: "steam_user",
111 steam_id = _steam_id,
112 function = stringify!($method),
113 action = stringify!($action),
114 status = "ok",
115 source = _source,
116 duration_ms = duration,
117 request = _input.as_str(),
118 response = output_str.as_str(),
119 "API call completed"
120 );
121 return Ok(v);
122 }
123 Err(e) => e,
124 };
125
126 let is_network_or_rate_limit = last_error.is_retryable();
127 let is_safe_action = ApiAction::$action.is_read_only();
128
129 if is_network_or_rate_limit && is_safe_action {
130 #[cfg(feature = "remote")]
131 if let Some(remote) = &self.remote {
132 _source = "remote";
133 match SteamUserApi::$method(remote, $($arg.clone()),*).await {
134 Ok(v) => {
135 let duration = i64::try_from(_start.elapsed().as_millis()).unwrap_or(i64::MAX);
136 let output_str = format!("{:?}", &v);
137 tracing::info!(
138 target: "steam_user",
139 steam_id = _steam_id,
140 function = stringify!($method),
141 action = stringify!($action),
142 status = "ok",
143 source = _source,
144 duration_ms = duration,
145 request = _input.as_str(),
146 response = output_str.as_str(),
147 "API call completed"
148 );
149 return Ok(v);
150 }
151 Err(e) => {
152 last_error = SteamUserError::RemoteFailed(Box::new(e));
153 }
154 }
155 }
156
157 #[cfg(feature = "gas")]
158 if let Some(gas) = &self.gas {
159 _source = "gas";
160 match SteamUserApi::$method(gas, $($arg),*).await {
161 Ok(v) => {
162 let duration = i64::try_from(_start.elapsed().as_millis()).unwrap_or(i64::MAX);
163 let output_str = format!("{:?}", &v);
164 tracing::info!(
165 target: "steam_user",
166 steam_id = _steam_id,
167 function = stringify!($method),
168 action = stringify!($action),
169 status = "ok",
170 source = _source,
171 duration_ms = duration,
172 request = _input.as_str(),
173 response = output_str.as_str(),
174 "API call completed"
175 );
176 return Ok(v);
177 }
178 Err(e) => {
179 last_error = SteamUserError::GasFailed(Box::new(e));
180 }
181 }
182 }
183 }
184
185 let duration = i64::try_from(_start.elapsed().as_millis()).unwrap_or(i64::MAX);
186 let err_str = format!("{}", last_error);
187 tracing::warn!(
188 target: "steam_user",
189 steam_id = _steam_id,
190 function = stringify!($method),
191 action = stringify!($action),
192 status = "error",
193 source = _source,
194 duration_ms = duration,
195 request = _input.as_str(),
196 error = err_str.as_str(),
197 "API call failed"
198 );
199
200 Err(last_error).with_action(ApiAction::$action)
201 }
202 )*
203 }
204 };
205}
206
207fallback_methods!(
208 get_account_details() -> AccountDetails => GetAccountDetails;
209 get_steam_wallet_balance() -> WalletBalance => GetSteamWalletBalance;
210 get_amount_spent_on_steam() -> String => GetAmountSpentOnSteam;
211 get_purchase_history() -> Vec<PurchaseHistoryItem> => GetPurchaseHistory;
212 redeem_wallet_code(code: &str) -> RedeemWalletCodeResponse => RedeemWalletCode;
213 parental_unlock(pin: &str) -> () => ParentalUnlock;
214 get_friend_activity(start: Option<u64>) -> FriendActivityResponse => GetFriendActivity;
215 get_friend_activity_full() -> Vec<FriendActivity> => GetFriendActivityFull;
216 comment_user_received_new_game(steam_id: SteamID, thread_id: u64, comment: &str) -> ActivityCommentResponse => CommentUserReceivedNewGame;
217 rate_up_user_received_new_game(steam_id: SteamID, thread_id: u64) -> ActivityCommentResponse => RateUpUserReceivedNewGame;
218 delete_comment_user_received_new_game(steam_id: SteamID, thread_id: u64, comment_id: &str) -> ActivityCommentResponse => DeleteCommentUserReceivedNewGame;
219 get_owned_apps() -> Vec<OwnedApp> => GetOwnedApps;
220 get_owned_apps_id() -> Vec<u32> => GetOwnedAppsId;
221 get_owned_apps_detail() -> Vec<OwnedAppDetail> => GetOwnedAppsDetail;
222 get_app_detail(app_ids: &[u32]) -> HashMap<u32, AppDetail> => GetAppDetail;
223 fetch_csgo_account_stats() -> CsgoAccountStats => FetchCsgoAccountStats;
224 get_app_list() -> SimpleSteamAppList => GetAppList;
225 suggest_app_list(term: &str) -> Vec<AppListItem> => SuggestAppList;
226 query_app_list(term: &str) -> Vec<AppListItem> => QueryAppList;
227 get_steam_app_version_info(app_id: u32) -> SteamAppVersionInfo => GetSteamAppVersionInfo;
228 get_dynamic_store_user_data() -> DynamicStoreUserData => GetDynamicStoreUserData;
229 fetch_batched_loyalty_reward_items(app_ids: &[u32]) -> Vec<steam_protos::messages::CLoyaltyRewardsBatchedQueryRewardItemsResponseResponse> => FetchBatchedLoyaltyRewardItems;
230 get_my_comments() -> Vec<UserComment> => GetMyComments;
231 get_user_comments(steam_id: SteamID) -> Vec<UserComment> => GetUserComments;
232 post_comment(steam_id: SteamID, message: &str) -> Option<UserComment> => PostComment;
233 delete_comment(steam_id: SteamID, gidcomment: &str) -> () => DeleteComment;
234 get_confirmations(identity_secret: &str, tag: Option<&str>) -> Vec<Confirmation> => GetConfirmations;
235 accept_confirmation_for_object(identity_secret: &str, object_id: u64) -> () => AcceptConfirmationForObject;
236 deny_confirmation_for_object(identity_secret: &str, object_id: u64) -> () => DenyConfirmationForObject;
237 get_account_email() -> String => GetAccountEmail;
238 get_current_steam_login() -> String => GetCurrentSteamLogin;
239 add_friend(steam_id: SteamID) -> () => AddFriend;
240 remove_friend(steam_id: SteamID) -> () => RemoveFriend;
241 accept_friend_request(steam_id: SteamID) -> () => AcceptFriendRequest;
242 ignore_friend_request(steam_id: SteamID) -> () => IgnoreFriendRequest;
243 block_user(steam_id: SteamID) -> () => BlockUser;
244 unblock_user(steam_id: SteamID) -> () => UnblockUser;
245 get_friends_list() -> HashMap<SteamID, i32> => GetFriendsList;
246 get_friends_details() -> FriendListPage => GetFriendsDetails;
247 get_friends_details_of_user(steam_id: SteamID) -> FriendListPage => GetFriendsDetailsOfUser;
248 search_users(query: &str, page: u32) -> CommunitySearchResult => SearchUsers;
249 create_instant_invite() -> String => CreateInstantInvite;
250 follow_user(steam_id: SteamID) -> () => FollowUser;
251 unfollow_user(steam_id: SteamID) -> () => UnfollowUser;
252 get_following_list() -> FriendListPage => GetFollowingList;
253 get_following_list_of_user(steam_id: SteamID) -> FriendListPage => GetFollowingListOfUser;
254 get_my_friends_id_list() -> Vec<SteamID> => GetMyFriendsIdList;
255 get_pending_friend_list() -> PendingFriendList => GetPendingFriendList;
256 remove_friends(steam_ids: &[SteamID]) -> () => RemoveFriends;
257 unfollow_users(steam_ids: &[SteamID]) -> () => UnfollowUsers;
258 cancel_friend_request(steam_id: SteamID) -> () => CancelFriendRequest;
259 get_friends_in_common(steam_id: SteamID) -> Vec<FriendDetails> => GetFriendsInCommon;
260 join_group(group_id: SteamID) -> () => JoinGroup;
261 leave_group(group_id: SteamID) -> () => LeaveGroup;
262 get_group_members(group_id: SteamID) -> Vec<SteamID> => GetGroupMembers;
263 post_group_announcement(group_id: SteamID, headline: &str, content: &str) -> () => PostGroupAnnouncement;
264 kick_group_member(group_id: SteamID, member_id: SteamID) -> () => KickGroupMember;
265 invite_user_to_group(user_id: SteamID, group_id: SteamID) -> () => InviteUserToGroup;
266 invite_users_to_group(user_ids: &[SteamID], group_id: SteamID) -> () => InviteUsersToGroup;
267 accept_group_invite(group_id: SteamID) -> () => AcceptGroupInvite;
268 ignore_group_invite(group_id: SteamID) -> () => IgnoreGroupInvite;
269 get_group_overview(gid: Option<SteamID>, group_url: Option<&str>, page: Option<i32>, search_key: Option<&str>) -> GroupOverview => GetGroupOverview;
270 get_group_steam_id_from_vanity_url(vanity_url: &str) -> String => GetGroupSteamIdFromVanityUrl;
271 get_group_info_xml(gid: Option<SteamID>, group_url: Option<&str>, page: Option<u32>) -> GroupInfoXml => GetGroupInfoXml;
272 get_group_info_xml_full(gid: Option<SteamID>, group_url: Option<&str>) -> GroupInfoXml => GetGroupInfoXmlFull;
273 get_invitable_groups(user_steam_id: SteamID) -> Vec<InvitableGroup> => GetInvitableGroups;
274 invite_all_friends_to_group(group_id: SteamID) -> () => InviteAllFriendsToGroup;
275 get_inventory(appid: AppId, context_id: ContextId) -> Vec<EconItem> => GetInventory;
276 get_user_inventory_contents(steam_id: SteamID, appid: AppId, context_id: ContextId) -> Vec<EconItem> => GetUserInventoryContents;
277 get_inventory_history() -> InventoryHistoryResult => GetInventoryHistory;
278 get_price_overview(appid: AppId, market_hash_name: &str) -> PriceOverview => GetPriceOverview;
279 get_active_inventories() -> Vec<ActiveInventory> => GetActiveInventories;
280 get_inventory_trading(appid: AppId, context_id: ContextId) -> serde_json::Value => GetInventoryTrading;
281 get_inventory_trading_partner(appid: AppId, partner: SteamID, context_id: ContextId) -> serde_json::Value => GetInventoryTradingPartner;
282 get_full_inventory_history() -> Vec<InventoryHistoryItem> => GetFullInventoryHistory;
283 get_my_listings() -> MyListingsResult => GetMyListings;
284 get_market_history(start: u32, count: u32) -> MarketHistoryResponse => GetMarketHistory;
285 sell_item(appid: AppId, contextid: ContextId, assetid: AssetId, amount: Amount, price: PriceCents) -> SellItemResult => SellItem;
286 remove_listing(listing_id: &str) -> bool => RemoveListing;
287 get_gem_value(appid: AppId, assetid: AssetId) -> GemValue => GetGemValue;
288 turn_item_into_gems(appid: AppId, assetid: AssetId, expected_value: u32) -> GemResult => TurnItemIntoGems;
289 get_booster_pack_catalog() -> Vec<BoosterPackEntry> => GetBoosterPackCatalog;
290 create_booster_pack(appid: AppId, use_untradable_gems: bool) -> BoosterResult => CreateBoosterPack;
291 open_booster_pack(appid: AppId, assetid: AssetId) -> Vec<EconItem> => OpenBoosterPack;
292 get_market_restrictions() -> (MarketRestrictions, Option<WalletBalance>) => GetMarketRestrictions;
293 get_market_apps() -> HashMap<u32, String> => GetMarketApps;
294 get_item_nameid(app_id: AppId, market_hash_name: &str) -> ItemNameId => GetItemNameid;
295 get_item_orders_histogram(item_nameid: ItemNameId, country: &str, currency: u32) -> ItemOrdersHistogramResponse => GetItemOrdersHistogram;
296 get_phone_number_status() -> Option<String> => GetPhoneNumberStatus;
297 add_phone_number(phone: &str) -> AddPhoneNumberResponse => AddPhoneNumber;
298 confirm_phone_code_for_add(code: &str) -> ConfirmPhoneCodeResponse => ConfirmPhoneCodeForAdd;
299 resend_phone_verification_code() -> serde_json::Value => ResendPhoneVerificationCode;
300 get_remove_phone_number_type() -> Option<RemovePhoneResult> => GetRemovePhoneNumberType;
301 send_account_recovery_code(wizard_param: serde_json::Value, method: i32) -> serde_json::Value => SendAccountRecoveryCode;
302 confirm_remove_phone_number_code(wizard_param: serde_json::Value, code: &str) -> serde_json::Value => ConfirmRemovePhoneNumberCode;
303 send_confirmation_2_steam_mobile_app(wizard_param: serde_json::Value) -> serde_json::Value => SendConfirmation2SteamMobileApp;
304 send_confirmation_2_steam_mobile_app_final(wizard_param: serde_json::Value) -> serde_json::Value => SendConfirmation2SteamMobileAppFinal;
305 get_privacy_settings() -> PrivacySettings => GetPrivacySettings;
306 set_privacy_settings(settings: PrivacySettings) -> PrivacySettings => SetPrivacySettings;
307 set_all_privacy(level: &str) -> PrivacySettings => SetAllPrivacy;
308 get_profile(steam_id: Option<SteamID>) -> SteamProfile => GetProfile;
309 edit_profile(settings: serde_json::Value) -> () => EditProfile;
310 set_persona_name(name: &str) -> () => SetPersonaName;
311 get_alias_history(steam_id: SteamID) -> Vec<AliasEntry> => GetAliasHistory;
312 clear_previous_aliases() -> () => ClearPreviousAliases;
313 set_nickname(steam_id: SteamID, nickname: &str) -> () => SetNickname;
314 remove_nickname(steam_id: SteamID) -> () => RemoveNickname;
315 post_profile_status(text: &str, app_id: Option<u32>) -> u64 => PostProfileStatus;
316 select_previous_avatar(avatar_hash: &str) -> () => SelectPreviousAvatar;
317 setup_profile() -> bool => SetupProfile;
318 get_user_summary_from_xml(steam_id: SteamID) -> UserSummaryXml => GetUserSummaryFromXml;
319 get_user_summary_from_profile(steam_id: Option<SteamID>) -> UserSummaryProfile => GetUserSummaryFromProfile;
320 fetch_full_profile(steam_id: SteamID) -> SteamProfile => FetchFullProfile;
321 resolve_user(steam_id: SteamID) -> Option<SteamUserProfile> => ResolveUser;
322 get_avatar_history() -> Vec<AvatarHistoryEntry> => GetAvatarHistory;
323 upload_avatar_from_url(url: &str) -> AvatarUploadResponse => UploadAvatarFromUrl;
324 enumerate_tokens() -> steam_protos::CAuthenticationRefreshTokenEnumerateResponse => EnumerateTokens;
325 check_token_exists(token_id: &str) -> bool => CheckTokenExists;
326 revoke_tokens(token_ids: &[&str], shared_secret: Option<&str>) -> crate::services::tokens::RevokeTokensResult => RevokeTokens;
327 get_trade_url() -> Option<String> => GetTradeUrl;
328 get_trade_offer() -> TradeOffersResponse => GetTradeOffer;
329 accept_trade_offer(trade_offer_id: u64, partner_steam_id: Option<String>) -> String => AcceptTradeOffer;
330 decline_trade_offer(trade_offer_id: u64) -> () => DeclineTradeOffer;
331 send_trade_offer(trade_url: &str, my_assets: Vec<TradeOfferAsset>, their_assets: Vec<TradeOfferAsset>, message: &str) -> TradeOfferResult => SendTradeOffer;
332 get_steam_guard_status() -> SteamGuardStatus => GetSteamGuardStatus;
333 enable_two_factor() -> TwoFactorResponse => EnableTwoFactor;
334 finalize_two_factor(shared_secret: &str, activation_code: &str) -> () => FinalizeTwoFactor;
335 disable_two_factor(revocation_code: &str) -> () => DisableTwoFactor;
336 deauthorize_devices() -> () => DeauthorizeDevices;
337 add_authenticator() -> TwoFactorResponse => AddAuthenticator;
338 finalize_authenticator(activation_code: &str) -> () => FinalizeAuthenticator;
339 remove_authenticator(revocation_code: &str) -> () => RemoveAuthenticator;
340 enable_steam_guard_email() -> bool => EnableSteamGuardEmail;
341 disable_steam_guard_email() -> bool => DisableSteamGuardEmail;
342 get_player_reports() -> Vec<PlayerReport> => GetPlayerReports;
343 add_free_license(package_id: u32) -> bool => AddFreeLicense;
344 add_sub_free_license(sub_id: u32) -> bool => AddSubFreeLicense;
345 redeem_points(definition_id: u32) -> steam_protos::messages::loyalty_rewards::CLoyaltyRewardsRedeemPointsResponse => RedeemPoints;
346 get_help_requests() -> Vec<HelpRequest> => GetHelpRequests;
347 get_help_request_detail(id: &str) -> String => GetHelpRequestDetail;
348 get_match_history(match_type: &str, token: Option<&str>) -> MatchHistoryResponse => GetMatchHistory;
349 logged_in() -> LoggedInResult => LoggedIn;
350 get_notifications() -> Notifications => GetNotifications;
351 get_web_api_key(domain: &str) -> String => GetWebApiKey;
352 resolve_vanity_url(api_key: &str, vanity_name: &str) -> SteamID => ResolveVanityUrl;
353 revoke_web_api_key() -> () => RevokeWebApiKey;
354);