diff --git a/classes/CSteamSharedFile.js b/classes/CSteamSharedFile.js index 277dbb9..6ef29cb 100644 --- a/classes/CSteamSharedFile.js +++ b/classes/CSteamSharedFile.js @@ -22,6 +22,8 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) { fileSize: null, postDate: null, resolution: null, + categories: [], + tags: [], uniqueVisitorsCount: null, favoritesCount: null, upvoteCount: null, @@ -39,6 +41,27 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) { // Load output into cheerio to make parsing easier let $ = Cheerio.load(body); + + // Determine type by looking at the second breadcrumb. Find the first separator as it has a unique name and go to the next element which holds our value of interest + let breadcrumb = $(".breadcrumbs > .breadcrumb_separator").next().get(0).children[0].data || ""; + + if (breadcrumb.includes("Screenshot")) { + sharedfile.type = ESharedFileType.Screenshot; + } + + if (breadcrumb.includes("Artwork")) { + sharedfile.type = ESharedFileType.Artwork; + } + + if (breadcrumb.includes("Guide")) { + sharedfile.type = ESharedFileType.Guide; + } + + if (breadcrumb.includes("Workshop")) { + sharedfile.type = ESharedFileType.Workshop; + } + + // Dynamically map detailsStatsContainerLeft to detailsStatsContainerRight in an object to make readout easier. It holds size, post date and resolution. let detailsStatsObj = {}; let detailsLeft = $(".detailsStatsContainerLeft").children(); @@ -52,6 +75,7 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) { detailsStatsObj[detailsLeft[e].children[0].data.trim()] = detailsRight[e].children[0].data; }); + // Dynamically map stats_table descriptions to values. This holds Unique Visitors and Current Favorites let statsTableObj = {}; let statsTable = $(".stats_table").children(); @@ -82,8 +106,28 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) { sharedfile.postDate = Helpers.decodeSteamTime(posted); - // Find resolution if artwork or screenshot - sharedfile.resolution = detailsStatsObj["Size"] || null; + // Find resolution if artwork or screenshot. Guides don't have a resolution and workshop items display it somewhere else + if (sharedfile.type != ESharedFileType.Workshop) { + sharedfile.resolution = detailsStatsObj["Size"] || null; + } else { + let resolutionTag = $(".workshopTagsTitle:contains(\"Resolution:\")").next(); + + sharedfile.resolution = resolutionTag.text() || null; // Keep prop null if this workshop item does not have a resolution + } + + + // Find categories if guide or workshop item + if (sharedfile.type == ESharedFileType.Guide || sharedfile.type == ESharedFileType.Workshop) { + let categoryTag = $(".workshopTagsTitle:contains(\"Category:\")").parent().contents().slice(1).text(); // Find div containing 'Category:' workshopTagsTitle, remove first element 'Category:' and get everything else as text + + sharedfile.categories = categoryTag ? categoryTag.split(", ") : []; // Convert to array if string is not empty (aka no categories have been found) + } + + + // Find tags (there can be multiple) + let tagsTag = $(".workshopTagsTitle:contains(\"Tags:\")").next().contents(); + + sharedfile.tags = tagsTag.map((i, e) => e.type === 'text' ? $(e).text() : '').get() || []; // Map text to an array - https://stackoverflow.com/a/31543727 // Find uniqueVisitorsCount. We can't use ' || null' here as Number("0") casts to false @@ -113,24 +157,11 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) { // Determine if this account has already voted on this sharedfile - sharedfile.isUpvoted = String($(".workshopItemControlCtn > #VoteUpBtn")[0].attribs["class"]).includes("toggled"); // Check if upvote btn class contains "toggled" - sharedfile.isDownvoted = String($(".workshopItemControlCtn > #VoteDownBtn")[0].attribs["class"]).includes("toggled"); // Check if downvote btn class contains "toggled" + const voteUpBtn = $(".workshopItemControlCtn > #VoteUpBtn")[0] || $(".greenlight_controls > #VoteUpBtn")[0]; // workshopItemControlCtn for "normal" items, greenlight_controls for items which can be voted into a game (e.g. CS skins) + const voteDownBtn = $(".workshopItemControlCtn > #VoteDownBtn")[0] || $(".greenlight_controls > #VoteDownBtn")[0]; - - // Determine type by looking at the second breadcrumb. Find the first separator as it has a unique name and go to the next element which holds our value of interest - let breadcrumb = $(".breadcrumbs > .breadcrumb_separator").next().get(0).children[0].data || ""; - - if (breadcrumb.includes("Screenshot")) { - sharedfile.type = ESharedFileType.Screenshot; - } - - if (breadcrumb.includes("Artwork")) { - sharedfile.type = ESharedFileType.Artwork; - } - - if (breadcrumb.includes("Guide")) { - sharedfile.type = ESharedFileType.Guide; - } + sharedfile.isUpvoted = String(voteUpBtn.attribs["class"]).includes("toggled"); // Check if upvote btn class contains "toggled" + sharedfile.isDownvoted = String(voteDownBtn.attribs["class"]).includes("toggled"); // Check if downvote btn class contains "toggled" // Find owner profile link, convert to steamID64 using SteamIdResolver lib and create a SteamID object @@ -158,7 +189,7 @@ SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) { * Constructor - Creates a new SharedFile object * @class * @param {SteamCommunity} community - * @param {{ id: string, type: ESharedFileType, appID: number, owner: SteamID|null, fileSize: string|null, postDate: number, resolution: string|null, uniqueVisitorsCount: number, favoritesCount: number, upvoteCount: number|null, guideNumRatings: Number|null, isUpvoted: boolean, isDownvoted: boolean }} data + * @param {{ id: string, type: ESharedFileType, appID: number, owner: SteamID|null, fileSize: string|null, postDate: number, resolution: string|null, category: string[], tags: string[], uniqueVisitorsCount: number, favoritesCount: number, upvoteCount: number|null, guideNumRatings: Number|null, isUpvoted: boolean, isDownvoted: boolean }} data */ function CSteamSharedFile(community, data) { /** @@ -200,7 +231,7 @@ CSteamSharedFile.prototype.comment = function(message, callback) { * Subscribes to this sharedfile's comment section. Note: Checkbox on webpage does not update * @param {function} callback - Takes only an Error object/null as the first argument */ -CSteamSharedFile.prototype.subscribe = function(callback) { +CSteamSharedFile.prototype.subscribeComments = function(callback) { this._community.subscribeSharedFileComments(this.owner, this.id, callback); }; @@ -216,6 +247,22 @@ CSteamSharedFile.prototype.unfavorite = function(callback) { * Unsubscribes from this sharedfile's comment section. Note: Checkbox on webpage does not update * @param {function} callback - Takes only an Error object/null as the first argument */ -CSteamSharedFile.prototype.unsubscribe = function(callback) { +CSteamSharedFile.prototype.unsubscribeComments = function(callback) { this._community.unsubscribeSharedFileComments(this.owner, this.id, callback); }; + +/** + * Subscribes to this workshop item + * @param {function} callback - Takes only an Error object/null as the first argument + */ +CSteamSharedFile.prototype.subscribeWorkshop = function(callback) { + this._community.subscribeWorkshopSharedFile(this.id, this.appID, callback); +}; + +/** + * Unsubscribes from this workshop item + * @param {function} callback - Takes only an Error object/null as the first argument + */ +CSteamSharedFile.prototype.unsubscribeWorkshop = function(callback) { + this._community.unsubscribeWorkshopSharedFile(this.id, this.appID, callback); +}; diff --git a/components/sharedfiles.js b/components/sharedfiles.js index b4fa2c7..60fb633 100644 --- a/components/sharedfiles.js +++ b/components/sharedfiles.js @@ -132,7 +132,7 @@ SteamCommunity.prototype.unfavoriteSharedFile = function(sharedFileId, appid, ca }; /** - * Unsubscribes from a sharedfile's comment section. Note: Checkbox on webpage does not update + * Unsubscribes from a sharedfile's comment section. * @param {SteamID | String} userID - ID of the user associated to this sharedfile * @param {String} sharedFileId - ID of the sharedfile * @param {function} callback - Takes only an Error object/null as the first argument @@ -156,3 +156,49 @@ SteamCommunity.prototype.unsubscribeSharedFileComments = function(userID, shared callback(err); }, "steamcommunity"); }; + +/** + * Subscribes to a workshop item sharedfile. + * @param {String} sharedFileId - ID of the sharedfile + * @param {String} appid - ID of the app associated to this sharedfile + * @param {function} callback - Takes only an Error object/null as the first argument + */ +SteamCommunity.prototype.subscribeWorkshopSharedFile = function(sharedFileId, appid, callback) { + this.httpRequestPost({ + "uri": "https://steamcommunity.com/sharedfiles/subscribe", + "form": { + "id": sharedFileId, + "appid": appid, + "sessionid": this.getSessionID() + } + }, function(err, response, body) { + if (!callback) { + return; + } + + callback(err); + }, "steamcommunity"); +}; + +/** + * Unsubscribes from a workshop item sharedfile. + * @param {String} sharedFileId - ID of the sharedfile + * @param {String} appid - ID of the app associated to this sharedfile + * @param {function} callback - Takes only an Error object/null as the first argument + */ +SteamCommunity.prototype.unsubscribeWorkshopSharedFile = function(sharedFileId, appid, callback) { + this.httpRequestPost({ + "uri": "https://steamcommunity.com/sharedfiles/unsubscribe", + "form": { + "id": sharedFileId, + "appid": appid, + "sessionid": this.getSessionID() + } + }, function(err, response, body) { + if (!callback) { + return; + } + + callback(err); + }, "steamcommunity"); +}; diff --git a/resources/ESharedFileType.js b/resources/ESharedFileType.js index 21290c8..01f006a 100644 --- a/resources/ESharedFileType.js +++ b/resources/ESharedFileType.js @@ -5,9 +5,11 @@ module.exports = { "Screenshot": 0, "Artwork": 1, "Guide": 2, + "Workshop": 3, // Value-to-name mapping for convenience "0": "Screenshot", "1": "Artwork", - "2": "Guide" + "2": "Guide", + "3": "Workshop" };