diff --git a/Cargo.toml b/Cargo.toml index b1ea9d8..0ee66c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ napi = { version = "2.13.1", features = ["tokio_rt", "napi6", "serde-json"] } napi-derive = "2.13.0" lazy_static = "1" tokio = { version = "1", features = ["sync", "time"] } -steamworks = { version="0.11.0", features = [ "serde", ] } +steamworks = { version = "0.11.0", features = ["serde"] } serde = "1" serde_json = "1" diff --git a/client.d.ts b/client.d.ts index 3673a3f..149ea23 100644 --- a/client.d.ts +++ b/client.d.ts @@ -297,6 +297,78 @@ export namespace workshop { * @returns an array of subscribed workshop item ids */ export function getSubscribedItems(): Array + export const enum UGCQueryType { + RankedByVote = 0, + RankedByPublicationDate = 1, + AcceptedForGameRankedByAcceptanceDate = 2, + RankedByTrend = 3, + FavoritedByFriendsRankedByPublicationDate = 4, + CreatedByFriendsRankedByPublicationDate = 5, + RankedByNumTimesReported = 6, + CreatedByFollowedUsersRankedByPublicationDate = 7, + NotYetRated = 8, + RankedByTotalVotesAsc = 9, + RankedByVotesUp = 10, + RankedByTextSearch = 11, + RankedByTotalUniqueSubscriptions = 12, + RankedByPlaytimeTrend = 13, + RankedByTotalPlaytime = 14, + RankedByAveragePlaytimeTrend = 15, + RankedByLifetimeAveragePlaytime = 16, + RankedByPlaytimeSessionsTrend = 17, + RankedByLifetimePlaytimeSessions = 18, + RankedByLastUpdatedDate = 19 + } + export const enum UGCType { + Items = 0, + ItemsMtx = 1, + ItemsReadyToUse = 2, + Collections = 3, + Artwork = 4, + Videos = 5, + Screenshots = 6, + AllGuides = 7, + WebGuides = 8, + IntegratedGuides = 9, + UsableInGame = 10, + ControllerBindings = 11, + GameManagedItems = 12, + All = 13 + } + export const enum UserListType { + Published = 0, + VotedOn = 1, + VotedUp = 2, + VotedDown = 3, + Favorited = 4, + Subscribed = 5, + UsedOrPlayed = 6, + Followed = 7 + } + export const enum UserListOrder { + CreationOrderAsc = 0, + CreationOrderDesc = 1, + TitleAsc = 2, + LastUpdatedDesc = 3, + SubscriptionDateDesc = 4, + VoteScoreDesc = 5, + ForModeration = 6 + } + export interface WorkshopItemStatistic { + numSubscriptions?: bigint + numFavorites?: bigint + numFollowers?: bigint + numUniqueSubscriptions?: bigint + numUniqueFavorites?: bigint + numUniqueFollowers?: bigint + numUniqueWebsiteViews?: bigint + reportScore?: bigint + numSecondsPlayed?: bigint + numPlaytimeSessions?: bigint + numComments?: bigint + numSecondsPlayedDuringTimePeriod?: bigint + numPlaytimeSessionsDuringTimePeriod?: bigint + } export interface WorkshopItem { publishedFileId: bigint creatorAppId?: number @@ -308,6 +380,9 @@ export namespace workshop { timeCreated: number /** Time updated in unix epoch seconds format */ timeUpdated: number + /** Time when the user added the published item to their list (not always applicable), provided in Unix epoch format (time since Jan 1st, 1970). */ + timeAddedToUserList: number + visibility: UgcItemVisibility banned: boolean acceptedForUse: boolean tags: Array @@ -317,13 +392,34 @@ export namespace workshop { numDownvotes: number numChildren: number previewUrl?: string + statistics: WorkshopItemStatistic + } + export interface WorkshopPaginatedResult { + items: Array + returnedResults: number + totalResults: number + wasCached: boolean + } + export interface WorkshopItemsResult { + items: Array + wasCached: boolean } - export interface WorkshopItemQuery { + export interface WorkshopItemQueryConfig { cachedResponseMaxAge?: number includeMetadata?: boolean includeLongDescription?: boolean + includeAdditionalPreviews?: boolean + onlyIds?: boolean + onlyTotal?: boolean language?: string + matchAnyTag?: boolean + requiredTags?: Array + excludedTags?: Array + searchText?: string + rankedByTrendDays?: number } - export function getItem(item: bigint, query?: WorkshopItemQuery | undefined | null): Promise - export function getItems(items: Array, query?: WorkshopItemQuery | undefined | null): Promise> + export function getItem(item: bigint, queryConfig?: WorkshopItemQueryConfig | undefined | null): Promise + export function getItems(items: Array, queryConfig?: WorkshopItemQueryConfig | undefined | null): Promise + export function getAllItems(page: number, queryType: UGCQueryType, itemType: UGCType, creatorAppId: number, consumerAppId: number, queryConfig?: WorkshopItemQueryConfig | undefined | null): Promise + export function getUserItems(page: number, accountId: number, listType: UserListType, itemType: UGCType, sortOrder: UserListOrder, creatorAppId: number, consumerAppId: number, queryConfig?: WorkshopItemQueryConfig | undefined | null): Promise } diff --git a/sdk/redistributable_bin/linux32/libsteam_api.so b/sdk/redistributable_bin/linux32/libsteam_api.so index 687503b..9798846 100644 Binary files a/sdk/redistributable_bin/linux32/libsteam_api.so and b/sdk/redistributable_bin/linux32/libsteam_api.so differ diff --git a/sdk/redistributable_bin/linux64/libsteam_api.so b/sdk/redistributable_bin/linux64/libsteam_api.so index 4217a2e..85f66c6 100644 Binary files a/sdk/redistributable_bin/linux64/libsteam_api.so and b/sdk/redistributable_bin/linux64/libsteam_api.so differ diff --git a/sdk/redistributable_bin/osx/libsteam_api.dylib b/sdk/redistributable_bin/osx/libsteam_api.dylib index 5237be0..2d8957f 100644 Binary files a/sdk/redistributable_bin/osx/libsteam_api.dylib and b/sdk/redistributable_bin/osx/libsteam_api.dylib differ diff --git a/sdk/redistributable_bin/steam_api.dll b/sdk/redistributable_bin/steam_api.dll index 49f738b..1c65572 100644 Binary files a/sdk/redistributable_bin/steam_api.dll and b/sdk/redistributable_bin/steam_api.dll differ diff --git a/sdk/redistributable_bin/steam_api.lib b/sdk/redistributable_bin/steam_api.lib index 0cc7011..3f39a78 100644 Binary files a/sdk/redistributable_bin/steam_api.lib and b/sdk/redistributable_bin/steam_api.lib differ diff --git a/sdk/redistributable_bin/win64/steam_api64.dll b/sdk/redistributable_bin/win64/steam_api64.dll index 2b42812..ce20eea 100644 Binary files a/sdk/redistributable_bin/win64/steam_api64.dll and b/sdk/redistributable_bin/win64/steam_api64.dll differ diff --git a/sdk/redistributable_bin/win64/steam_api64.lib b/sdk/redistributable_bin/win64/steam_api64.lib index fb014ec..845cf57 100644 Binary files a/sdk/redistributable_bin/win64/steam_api64.lib and b/sdk/redistributable_bin/win64/steam_api64.lib differ diff --git a/src/api/workshop.rs b/src/api/workshop.rs index 7c96bb6..1204cad 100644 --- a/src/api/workshop.rs +++ b/src/api/workshop.rs @@ -16,6 +16,7 @@ pub mod workshop { pub needs_to_accept_agreement: bool, } + #[derive(Debug)] #[napi] pub enum UgcItemVisibility { Public, @@ -24,9 +25,20 @@ pub mod workshop { Unlisted, } - impl From for steamworks::PublishedFileVisibility { - fn from(visibility: UgcItemVisibility) -> Self { + impl From for UgcItemVisibility { + fn from(visibility: steamworks::PublishedFileVisibility) -> Self { match visibility { + steamworks::PublishedFileVisibility::Public => UgcItemVisibility::Public, + steamworks::PublishedFileVisibility::FriendsOnly => UgcItemVisibility::FriendsOnly, + steamworks::PublishedFileVisibility::Private => UgcItemVisibility::Private, + steamworks::PublishedFileVisibility::Unlisted => UgcItemVisibility::Unlisted, + } + } + } + + impl Into for UgcItemVisibility { + fn into(self) -> steamworks::PublishedFileVisibility { + match self { UgcItemVisibility::Public => steamworks::PublishedFileVisibility::Public, UgcItemVisibility::FriendsOnly => steamworks::PublishedFileVisibility::FriendsOnly, UgcItemVisibility::Private => steamworks::PublishedFileVisibility::Private, @@ -96,6 +108,7 @@ pub mod workshop { pub total: BigInt, } + #[derive(Debug)] #[napi] pub enum UpdateStatus { Invalid, diff --git a/src/api/workshop_item.rs b/src/api/workshop_item.rs index a6643f1..11516c2 100644 --- a/src/api/workshop_item.rs +++ b/src/api/workshop_item.rs @@ -2,11 +2,258 @@ use napi_derive::napi; #[napi] pub mod workshop { - use napi::bindgen_prelude::{BigInt, Error}; - use steamworks::PublishedFileId; + use napi::bindgen_prelude::{BigInt, Error, FromNapiValue, ToNapiValue}; + use steamworks::{AccountId, PublishedFileId}; use tokio::sync::oneshot; use crate::api::localplayer::PlayerSteamId; + use crate::api::workshop::workshop::UgcItemVisibility; + + #[napi] + pub enum UGCQueryType { + RankedByVote, + RankedByPublicationDate, + AcceptedForGameRankedByAcceptanceDate, + RankedByTrend, + FavoritedByFriendsRankedByPublicationDate, + CreatedByFriendsRankedByPublicationDate, + RankedByNumTimesReported, + CreatedByFollowedUsersRankedByPublicationDate, + NotYetRated, + RankedByTotalVotesAsc, + RankedByVotesUp, + RankedByTextSearch, + RankedByTotalUniqueSubscriptions, + RankedByPlaytimeTrend, + RankedByTotalPlaytime, + RankedByAveragePlaytimeTrend, + RankedByLifetimeAveragePlaytime, + RankedByPlaytimeSessionsTrend, + RankedByLifetimePlaytimeSessions, + RankedByLastUpdatedDate, + } + + impl Into for UGCQueryType { + fn into(self: UGCQueryType) -> steamworks::UGCQueryType { + match self { + UGCQueryType::RankedByVote => steamworks::UGCQueryType::RankedByVote, + UGCQueryType::RankedByPublicationDate => { + steamworks::UGCQueryType::RankedByPublicationDate + } + UGCQueryType::AcceptedForGameRankedByAcceptanceDate => { + steamworks::UGCQueryType::AcceptedForGameRankedByAcceptanceDate + } + UGCQueryType::RankedByTrend => steamworks::UGCQueryType::RankedByTrend, + UGCQueryType::FavoritedByFriendsRankedByPublicationDate => { + steamworks::UGCQueryType::FavoritedByFriendsRankedByPublicationDate + } + UGCQueryType::CreatedByFriendsRankedByPublicationDate => { + steamworks::UGCQueryType::CreatedByFriendsRankedByPublicationDate + } + UGCQueryType::RankedByNumTimesReported => { + steamworks::UGCQueryType::RankedByNumTimesReported + } + UGCQueryType::CreatedByFollowedUsersRankedByPublicationDate => { + steamworks::UGCQueryType::CreatedByFollowedUsersRankedByPublicationDate + } + UGCQueryType::NotYetRated => steamworks::UGCQueryType::NotYetRated, + UGCQueryType::RankedByTotalVotesAsc => { + steamworks::UGCQueryType::RankedByTotalVotesAsc + } + UGCQueryType::RankedByVotesUp => steamworks::UGCQueryType::RankedByVotesUp, + UGCQueryType::RankedByTextSearch => steamworks::UGCQueryType::RankedByTextSearch, + UGCQueryType::RankedByTotalUniqueSubscriptions => { + steamworks::UGCQueryType::RankedByTotalUniqueSubscriptions + } + UGCQueryType::RankedByPlaytimeTrend => { + steamworks::UGCQueryType::RankedByPlaytimeTrend + } + UGCQueryType::RankedByTotalPlaytime => { + steamworks::UGCQueryType::RankedByTotalPlaytime + } + UGCQueryType::RankedByAveragePlaytimeTrend => { + steamworks::UGCQueryType::RankedByAveragePlaytimeTrend + } + UGCQueryType::RankedByLifetimeAveragePlaytime => { + steamworks::UGCQueryType::RankedByLifetimeAveragePlaytime + } + UGCQueryType::RankedByPlaytimeSessionsTrend => { + steamworks::UGCQueryType::RankedByPlaytimeSessionsTrend + } + UGCQueryType::RankedByLifetimePlaytimeSessions => { + steamworks::UGCQueryType::RankedByLifetimePlaytimeSessions + } + UGCQueryType::RankedByLastUpdatedDate => { + steamworks::UGCQueryType::RankedByLastUpdatedDate + } + } + } + } + + #[napi] + pub enum UGCType { + Items, + ItemsMtx, + ItemsReadyToUse, + Collections, + Artwork, + Videos, + Screenshots, + AllGuides, + WebGuides, + IntegratedGuides, + UsableInGame, + ControllerBindings, + GameManagedItems, + All, + } + + impl Into for UGCType { + fn into(self) -> steamworks::UGCType { + match self { + UGCType::Items => steamworks::UGCType::Items, + UGCType::ItemsMtx => steamworks::UGCType::ItemsMtx, + UGCType::ItemsReadyToUse => steamworks::UGCType::ItemsReadyToUse, + UGCType::Collections => steamworks::UGCType::Collections, + UGCType::Artwork => steamworks::UGCType::Artwork, + UGCType::Videos => steamworks::UGCType::Videos, + UGCType::Screenshots => steamworks::UGCType::Screenshots, + UGCType::AllGuides => steamworks::UGCType::AllGuides, + UGCType::WebGuides => steamworks::UGCType::WebGuides, + UGCType::IntegratedGuides => steamworks::UGCType::IntegratedGuides, + UGCType::UsableInGame => steamworks::UGCType::UsableInGame, + UGCType::ControllerBindings => steamworks::UGCType::ControllerBindings, + UGCType::GameManagedItems => steamworks::UGCType::GameManagedItems, + UGCType::All => steamworks::UGCType::All, + } + } + } + + #[napi] + pub enum UserListType { + Published, + VotedOn, + VotedUp, + VotedDown, + // Deprecated: WillVoteLater, + Favorited, + Subscribed, + UsedOrPlayed, + Followed, + } + + impl Into for UserListType { + fn into(self) -> steamworks::UserList { + match self { + UserListType::Published => steamworks::UserList::Published, + UserListType::VotedOn => steamworks::UserList::VotedOn, + UserListType::VotedUp => steamworks::UserList::VotedUp, + UserListType::VotedDown => steamworks::UserList::VotedDown, + // UserListType::WillVoteLater => steamworks::UserList::WillVoteLater, // Deprecated + UserListType::Favorited => steamworks::UserList::Favorited, + UserListType::Subscribed => steamworks::UserList::Subscribed, + UserListType::UsedOrPlayed => steamworks::UserList::UsedOrPlayed, + UserListType::Followed => steamworks::UserList::Followed, + } + } + } + + #[napi] + pub enum UserListOrder { + CreationOrderAsc, + CreationOrderDesc, + TitleAsc, + LastUpdatedDesc, + SubscriptionDateDesc, + VoteScoreDesc, + ForModeration, + } + + impl Into for UserListOrder { + fn into(self: UserListOrder) -> steamworks::UserListOrder { + match self { + UserListOrder::CreationOrderAsc => steamworks::UserListOrder::CreationOrderAsc, + UserListOrder::CreationOrderDesc => steamworks::UserListOrder::CreationOrderDesc, + UserListOrder::TitleAsc => steamworks::UserListOrder::TitleAsc, + UserListOrder::LastUpdatedDesc => steamworks::UserListOrder::LastUpdatedDesc, + UserListOrder::SubscriptionDateDesc => { + steamworks::UserListOrder::SubscriptionDateDesc + } + UserListOrder::VoteScoreDesc => steamworks::UserListOrder::VoteScoreDesc, + UserListOrder::ForModeration => steamworks::UserListOrder::ForModeration, + } + } + } + + #[derive(Debug)] + #[napi(object)] + pub struct WorkshopItemStatistic { + pub num_subscriptions: Option, // 0 gets the number of subscriptions. + pub num_favorites: Option, // 1 gets the number of favorites. + pub num_followers: Option, // 2 gets the number of followers. + pub num_unique_subscriptions: Option, // 3 gets the number of unique subscriptions. + pub num_unique_favorites: Option, // 4 gets the number of unique favorites. + pub num_unique_followers: Option, // 5 gets the number of unique followers. + pub num_unique_website_views: Option, // 6 gets the number of unique views the item has on its steam workshop page. + pub report_score: Option, // 7 gets the number of times the item has been reported. + pub num_seconds_played: Option, // 8 gets the total number of seconds this item has been used across all players. + pub num_playtime_sessions: Option, // 9 gets the total number of play sessions this item has been used in. + pub num_comments: Option, // 10 gets the number of comments on the items that steam has on its steam workshop page. + pub num_seconds_played_during_time_period: Option, // 11 gets the number of seconds this item has been used over the given time period. + pub num_playtime_sessions_during_time_period: Option, // 12 Gets the number of sessions this item has been used in over the given time period. + } + + impl WorkshopItemStatistic { + fn from_query_results(results: &steamworks::QueryResults, index: u32) -> Self { + Self { + num_subscriptions: results + .statistic(index, steamworks::UGCStatisticType::Subscriptions) + .map(|v| BigInt::from(v)), + num_favorites: results + .statistic(index, steamworks::UGCStatisticType::Favorites) + .map(|v| BigInt::from(v)), + num_followers: results + .statistic(index, steamworks::UGCStatisticType::Followers) + .map(|v| BigInt::from(v)), + num_unique_subscriptions: results + .statistic(index, steamworks::UGCStatisticType::UniqueSubscriptions) + .map(|v| BigInt::from(v)), + num_unique_favorites: results + .statistic(index, steamworks::UGCStatisticType::UniqueFavorites) + .map(|v| BigInt::from(v)), + num_unique_followers: results + .statistic(index, steamworks::UGCStatisticType::UniqueFollowers) + .map(|v| BigInt::from(v)), + num_unique_website_views: results + .statistic(index, steamworks::UGCStatisticType::UniqueWebsiteViews) + .map(|v| BigInt::from(v)), + report_score: results + .statistic(index, steamworks::UGCStatisticType::Reports) + .map(|v| BigInt::from(v)), + num_seconds_played: results + .statistic(index, steamworks::UGCStatisticType::SecondsPlayed) + .map(|v| BigInt::from(v)), + num_playtime_sessions: results + .statistic(index, steamworks::UGCStatisticType::PlaytimeSessions) + .map(|v| BigInt::from(v)), + num_comments: results + .statistic(index, steamworks::UGCStatisticType::Comments) + .map(|v| BigInt::from(v)), + num_seconds_played_during_time_period: results + .statistic( + index, + steamworks::UGCStatisticType::SecondsPlayedDuringTimePeriod, + ) + .map(|v| BigInt::from(v)), + num_playtime_sessions_during_time_period: results + .statistic( + index, + steamworks::UGCStatisticType::PlaytimeSessionsDuringTimePeriod, + ) + .map(|v| BigInt::from(v)), + } + } + } #[derive(Debug)] #[napi(object)] @@ -21,6 +268,9 @@ pub mod workshop { pub time_created: u32, /// Time updated in unix epoch seconds format pub time_updated: u32, + /// Time when the user added the published item to their list (not always applicable), provided in Unix epoch format (time since Jan 1st, 1970). + pub time_added_to_user_list: u32, + pub visibility: UgcItemVisibility, pub banned: bool, pub accepted_for_use: bool, pub tags: Vec, @@ -30,78 +280,165 @@ pub mod workshop { pub num_downvotes: u32, pub num_children: u32, pub preview_url: Option, + pub statistics: WorkshopItemStatistic, // Is it necessary to design this as optional? } impl WorkshopItem { - fn from_query(result: steamworks::QueryResult, preview_url: Option) -> Self { + fn from_query_results(results: &steamworks::QueryResults, index: u32) -> Option { + results.get(index).map(|item| Self { + published_file_id: BigInt::from(item.published_file_id.0), + creator_app_id: item.creator_app_id.map(|id| id.0), + consumer_app_id: item.consumer_app_id.map(|id| id.0), + title: item.title, + description: item.description, + owner: PlayerSteamId::from_steamid(item.owner), + time_created: item.time_created, + time_updated: item.time_updated, + time_added_to_user_list: item.time_added_to_user_list, + visibility: item.visibility.into(), + banned: item.banned, + accepted_for_use: item.accepted_for_use, + tags: item.tags, + tags_truncated: item.tags_truncated, + url: item.url, + num_upvotes: item.num_upvotes, + num_downvotes: item.num_downvotes, + num_children: item.num_children, + preview_url: results.preview_url(index), + statistics: WorkshopItemStatistic::from_query_results(results, index), + }) + } + } + + #[derive(Debug)] + #[napi(object)] + pub struct WorkshopPaginatedResult { + pub items: Vec>, + pub returned_results: u32, + pub total_results: u32, + pub was_cached: bool, + } + + impl WorkshopPaginatedResult { + fn from_query_results(query_results: steamworks::QueryResults) -> Self { Self { - published_file_id: BigInt::from(result.published_file_id.0), - creator_app_id: result.creator_app_id.map(|id| id.0), - consumer_app_id: result.consumer_app_id.map(|id| id.0), - title: result.title, - description: result.description, - owner: PlayerSteamId::from_steamid(result.owner), - time_created: result.time_created, - time_updated: result.time_updated, - banned: result.banned, - accepted_for_use: result.accepted_for_use, - tags: result.tags, - tags_truncated: result.tags_truncated, - url: result.url, - num_upvotes: result.num_upvotes, - num_downvotes: result.num_downvotes, - num_children: result.num_children, - preview_url, + items: (0..query_results.returned_results()) + .map(|i| WorkshopItem::from_query_results(&query_results, i)) + .collect(), + returned_results: query_results.returned_results(), + total_results: query_results.total_results(), + was_cached: query_results.was_cached(), } } } + #[derive(Debug)] + #[napi(object)] + pub struct WorkshopItemsResult { + pub items: Vec>, + pub was_cached: bool, + } + + impl WorkshopItemsResult { + fn from_query_results(query_results: steamworks::QueryResults) -> Self { + Self { + items: (0..query_results.returned_results()) + .map(|i| WorkshopItem::from_query_results(&query_results, i)) + .collect(), + was_cached: query_results.was_cached(), + } + } + } + + #[derive(Debug)] #[napi(object)] - pub struct WorkshopItemQuery { + pub struct WorkshopItemQueryConfig { pub cached_response_max_age: Option, pub include_metadata: Option, pub include_long_description: Option, + pub include_additional_previews: Option, + pub only_ids: Option, + pub only_total: Option, pub language: Option, + pub match_any_tag: Option, + pub required_tags: Option>, + pub excluded_tags: Option>, + pub search_text: Option, + pub ranked_by_trend_days: Option, + } + + fn handle_query_config( + mut query_handle: steamworks::QueryHandle, + query_config: Option, + ) -> steamworks::QueryHandle { + // Apply statistics query parameters if provided + if let Some(query_config) = query_config { + if let Some(cached_response_max_age) = query_config.cached_response_max_age { + query_handle = query_handle.set_allow_cached_response(cached_response_max_age); + } + if let Some(include_metadata) = query_config.include_metadata { + query_handle = query_handle.set_return_metadata(include_metadata); + } + if let Some(include_long_description) = query_config.include_long_description { + query_handle = query_handle.set_return_long_description(include_long_description); + } + if let Some(include_additional_previews) = query_config.include_additional_previews { + query_handle = + query_handle.set_return_additional_previews(include_additional_previews) + } + if let Some(only_ids) = query_config.only_ids { + query_handle = query_handle.set_return_only_ids(only_ids) + } + if let Some(only_total) = query_config.only_total { + query_handle = query_handle.set_return_total_only(only_total) + } + if let Some(language) = query_config.language { + query_handle = query_handle.set_language(&language); + } + if let Some(match_any_tag) = query_config.match_any_tag { + query_handle = query_handle.set_match_any_tag(match_any_tag); + } + if let Some(required_tags) = query_config.required_tags { + for tag in required_tags { + query_handle = query_handle.add_required_tag(&tag); + } + } + if let Some(excluded_tags) = query_config.excluded_tags { + for tag in excluded_tags { + query_handle = query_handle.add_excluded_tag(&tag); + } + } + if let Some(search_text) = query_config.search_text { + query_handle = query_handle.set_search_text(&search_text); + } + if let Some(ranked_by_trend_days) = query_config.ranked_by_trend_days { + query_handle = query_handle.set_ranked_by_trend_days(ranked_by_trend_days); + } + } + return query_handle; } #[napi] pub async fn get_item( item: BigInt, - query: Option, + query_config: Option, ) -> Result, Error> { let client = crate::client::get_client(); let (tx, rx) = oneshot::channel(); { - let mut item_query = client + let mut query_handle = client .ugc() .query_item(PublishedFileId(item.get_u64().1)) .map_err(|e| Error::from_reason(e.to_string()))?; - if let Some(query) = query { - if let Some(cached_response_max_age) = query.cached_response_max_age { - item_query = item_query.allow_cached_response(cached_response_max_age); - } + query_handle = handle_query_config(query_handle, query_config); - if let Some(include_metadata) = query.include_metadata { - item_query = item_query.include_metadata(include_metadata); - } - - if let Some(include_long_description) = query.include_long_description { - item_query = item_query.include_long_desc(include_long_description); - } - - if let Some(language) = query.language { - item_query = item_query.language(&language); - } - } - - item_query.fetch(|result| { - tx.send(result.map(|result| { - result - .get(0) - .map(|item| WorkshopItem::from_query(item, result.preview_url(0))) - })) + query_handle.fetch(|fetch_result| { + tx.send( + fetch_result + .map(|query_results| WorkshopItem::from_query_results(&query_results, 0)), + ) .unwrap(); }); } @@ -114,13 +451,13 @@ pub mod workshop { #[napi] pub async fn get_items( items: Vec, - query: Option, - ) -> Result>, Error> { + query_config: Option, + ) -> Result { let client = crate::client::get_client(); let (tx, rx) = oneshot::channel(); { - let mut item_query = client + let mut query_handle = client .ugc() .query_items( items @@ -130,35 +467,101 @@ pub mod workshop { ) .map_err(|e| Error::from_reason(e.to_string()))?; - if let Some(query) = query { - if let Some(cached_response_max_age) = query.cached_response_max_age { - item_query = item_query.allow_cached_response(cached_response_max_age); - } + query_handle = handle_query_config(query_handle, query_config); - if let Some(include_metadata) = query.include_metadata { - item_query = item_query.include_metadata(include_metadata); - } + query_handle.fetch(|fetch_result| { + tx.send( + fetch_result.map(|query_results| { + WorkshopItemsResult::from_query_results(query_results) + }), + ) + .unwrap(); + }); + } - if let Some(include_long_description) = query.include_long_description { - item_query = item_query.include_long_desc(include_long_description); - } + rx.await + .unwrap() + .map_err(|e| Error::from_reason(e.to_string())) + } - if let Some(language) = query.language { - item_query = item_query.language(&language); - } - } + #[napi] + pub async fn get_all_items( + page: u32, + query_type: UGCQueryType, + item_type: UGCType, + creator_app_id: u32, + consumer_app_id: u32, + query_config: Option, + ) -> Result { + let client = crate::client::get_client(); + let (tx, rx) = oneshot::channel(); - item_query.fetch(|result| { - tx.send(result.map(|result| { - result - .iter() - .enumerate() - .map(|(i, item)| { - item.map(|item| { - WorkshopItem::from_query(item, result.preview_url(i as u32)) - }) - }) - .collect() + { + // Start configuring the query for all items + let mut query_handle = client + .ugc() + .query_all( + query_type.into(), + item_type.into(), + steamworks::AppIDs::Both { + creator: steamworks::AppId(creator_app_id), + consumer: steamworks::AppId(consumer_app_id), + }, + page, + ) + .map_err(|e| Error::from_reason(e.to_string()))?; + + query_handle = handle_query_config(query_handle, query_config); + + query_handle.fetch(|fetch_result| { + tx.send(fetch_result.map(|query_results| { + WorkshopPaginatedResult::from_query_results(query_results) + })) + .unwrap(); + }); + } + + rx.await + .unwrap() + .map_err(|e| Error::from_reason(e.to_string())) + } + + #[napi] + pub async fn get_user_items( + page: u32, + account_id: u32, + list_type: UserListType, + item_type: UGCType, + sort_order: UserListOrder, + creator_app_id: u32, + consumer_app_id: u32, + query_config: Option, + ) -> Result { + let client = crate::client::get_client(); + let (tx, rx) = oneshot::channel(); + + { + // Start configuring the query for user items + let mut query_handle = client + .ugc() + .query_user( + AccountId::from_raw(account_id), + list_type.into(), + item_type.into(), + sort_order.into(), + steamworks::AppIDs::Both { + creator: steamworks::AppId(creator_app_id), + consumer: steamworks::AppId(consumer_app_id), + }, + page, + ) + .map_err(|e| Error::from_reason(e.to_string()))?; + + query_handle = handle_query_config(query_handle, query_config); + + query_handle.fetch(|fetch_result| { + tx.send(fetch_result.map(|query_results| { + WorkshopPaginatedResult::from_query_results(query_results) })) .unwrap(); }); diff --git a/src/lib.rs b/src/lib.rs index 86dfcc4..e56102e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ use napi::bindgen_prelude::Error; use napi_derive::napi; use steamworks::AppId; use steamworks::Client; +use steamworks::SteamAPIInitError; pub mod client; @@ -18,7 +19,11 @@ pub fn init(app_id: Option) -> Result<(), Error> { let (steam_client, steam_single) = app_id .map(|app_id| Client::init_app(AppId(app_id))) .unwrap_or_else(Client::init) - .map_err(|e| Error::from_reason(e.to_string()))?; + .map_err(|e| match e { + SteamAPIInitError::FailedGeneric(msg) + | SteamAPIInitError::NoSteamClient(msg) + | SteamAPIInitError::VersionMismatch(msg) => Error::from_reason(msg), + })?; steam_client.user_stats().request_current_stats();