node-steamcommunity/classes/CSteamSharedFile.js

222 lines
7.9 KiB
JavaScript
Raw Normal View History

2023-05-15 00:52:16 +08:00
const Cheerio = require('cheerio');
const SteamID = require('steamid');
2023-06-24 14:39:16 +08:00
2023-05-15 00:52:16 +08:00
const SteamCommunity = require('../index.js');
2023-06-24 14:39:16 +08:00
const Helpers = require('../components/helpers.js');
const ESharedFileType = require('../resources/ESharedFileType.js');
/**
* Scrape a sharedfile's DOM to get all available information
2023-06-24 14:39:16 +08:00
* @param {string} sharedFileId - ID of the sharedfile
* @param {function} callback - First argument is null/Error, second is object containing all available information
*/
2023-06-24 14:39:16 +08:00
SteamCommunity.prototype.getSteamSharedFile = function(sharedFileId, callback) {
2023-05-15 00:52:16 +08:00
// Construct object holding all the data we can scrape
let sharedfile = {
2023-05-16 04:45:33 +08:00
id: sharedFileId,
2023-05-15 00:52:16 +08:00
type: null,
appID: null,
owner: null,
fileSize: null,
postDate: null,
resolution: null,
uniqueVisitorsCount: null,
favoritesCount: null,
upvoteCount: null,
guideNumRatings: null,
isUpvoted: null,
isDownvoted: null
2023-05-15 00:52:16 +08:00
};
2023-05-15 00:52:16 +08:00
// Get DOM of sharedfile
2023-05-16 04:45:33 +08:00
this.httpRequestGet(`https://steamcommunity.com/sharedfiles/filedetails/?id=${sharedFileId}`, (err, res, body) => {
2023-05-15 00:52:16 +08:00
try {
2023-05-15 00:52:16 +08:00
/* --------------------- Preprocess output --------------------- */
2023-05-15 00:52:16 +08:00
// Load output into cheerio to make parsing easier
let $ = Cheerio.load(body);
2023-05-15 00:52:16 +08:00
// 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();
let detailsRight = $(".detailsStatsContainerRight").children();
2023-05-15 00:52:16 +08:00
Object.keys(detailsLeft).forEach((e) => { // Dynamically get all details. Don't hardcore so that this also works for guides.
if (isNaN(e)) {
return; // Ignore invalid entries
}
2023-05-15 00:52:16 +08:00
detailsStatsObj[detailsLeft[e].children[0].data.trim()] = detailsRight[e].children[0].data;
});
2023-05-15 00:52:16 +08:00
// Dynamically map stats_table descriptions to values. This holds Unique Visitors and Current Favorites
let statsTableObj = {};
let statsTable = $(".stats_table").children();
2023-05-15 00:52:16 +08:00
Object.keys(statsTable).forEach((e) => {
if (isNaN(e)) {
return; // Ignore invalid entries
}
2023-05-15 00:52:16 +08:00
// Value description is at index 3, value data at index 1
statsTableObj[statsTable[e].children[3].children[0].data] = statsTable[e].children[1].children[0].data.replace(/,/g, ""); // Remove commas from 1k+ values
});
2023-05-15 00:52:16 +08:00
/* --------------------- Find and map values --------------------- */
2023-05-15 00:52:16 +08:00
// Find appID in share button onclick event
2023-05-16 04:45:33 +08:00
sharedfile.appID = Number($("#ShareItemBtn").attr()["onclick"].replace(`ShowSharePublishedFilePopup( '${sharedFileId}', '`, "").replace("' );", ""));
2023-05-15 00:52:16 +08:00
// Find fileSize if not guide
sharedfile.fileSize = detailsStatsObj["File Size"] || null; // TODO: Convert to bytes? It seems like to always be MB but no guarantee
2023-05-15 00:52:16 +08:00
// Find postDate and convert to timestamp
let posted = detailsStatsObj["Posted"].trim();
2023-06-24 14:48:26 +08:00
sharedfile.postDate = Helpers.decodeSteamTime(posted);
2023-05-15 00:52:16 +08:00
// Find resolution if artwork or screenshot
sharedfile.resolution = detailsStatsObj["Size"] || null;
2023-05-15 00:52:16 +08:00
// Find uniqueVisitorsCount. We can't use ' || null' here as Number("0") casts to false
if (statsTableObj["Unique Visitors"]) {
sharedfile.uniqueVisitorsCount = Number(statsTableObj["Unique Visitors"]);
}
2023-05-15 00:52:16 +08:00
// Find favoritesCount. We can't use ' || null' here as Number("0") casts to false
if (statsTableObj["Current Favorites"]) {
sharedfile.favoritesCount = Number(statsTableObj["Current Favorites"]);
}
2023-05-15 00:52:16 +08:00
// Find upvoteCount. We can't use ' || null' here as Number("0") casts to false
let upvoteCount = $("#VotesUpCountContainer > #VotesUpCount").text();
2023-05-15 00:52:16 +08:00
if (upvoteCount) {
sharedfile.upvoteCount = Number(upvoteCount);
}
// Find numRatings if this is a guide as they use a different voting system
2023-05-30 01:29:24 +08:00
let numRatings = $(".ratingSection > .numRatings").text().replace(" ratings", "");
sharedfile.guideNumRatings = Number(numRatings) || null; // Set to null if not a guide or if the guide does not have enough ratings to show a value
// 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"
2023-05-15 00:52:16 +08:00
// 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 || "";
2023-05-15 00:52:16 +08:00
if (breadcrumb.includes("Screenshot")) {
2023-06-24 14:39:16 +08:00
sharedfile.type = ESharedFileType.Screenshot;
2023-05-15 00:52:16 +08:00
}
2023-05-15 00:52:16 +08:00
if (breadcrumb.includes("Artwork")) {
2023-06-24 14:39:16 +08:00
sharedfile.type = ESharedFileType.Artwork;
2023-05-15 00:52:16 +08:00
}
2023-05-15 00:52:16 +08:00
if (breadcrumb.includes("Guide")) {
2023-06-24 14:39:16 +08:00
sharedfile.type = ESharedFileType.Guide;
2023-05-15 00:52:16 +08:00
}
// Find owner profile link, convert to steamID64 using SteamIdResolver lib and create a SteamID object
let ownerHref = $(".friendBlockLinkOverlay").attr()["href"];
Helpers.resolveVanityURL(ownerHref, (err, data) => { // This request takes <1 sec
if (err) {
callback(err);
return;
2023-05-15 00:52:16 +08:00
}
sharedfile.owner = new SteamID(data.steamID);
2023-05-15 00:52:16 +08:00
// Make callback when ID was resolved as otherwise owner will always be null
2023-06-24 14:39:16 +08:00
callback(null, new CSteamSharedFile(this, sharedfile));
2023-05-15 00:52:16 +08:00
});
} catch (err) {
callback(err, null);
}
}, "steamcommunity");
2023-05-14 21:22:15 +08:00
};
2023-05-30 01:29:24 +08:00
/**
2023-06-24 14:39:16 +08:00
* Constructor - Creates a new SharedFile object
2023-05-30 01:29:24 +08:00
* @class
* @param {SteamCommunity} community
2023-06-24 14:39:16 +08:00
* @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
2023-05-30 01:29:24 +08:00
*/
2023-06-24 14:39:16 +08:00
function CSteamSharedFile(community, data) {
2023-05-30 01:29:24 +08:00
/**
* @type {SteamCommunity}
*/
2023-05-15 00:52:16 +08:00
this._community = community;
2023-06-24 14:39:16 +08:00
// Clone all the data we received
2023-05-30 01:29:24 +08:00
Object.assign(this, data);
2023-05-14 21:26:55 +08:00
}
/**
* Deletes a comment from this sharedfile's comment section
* @param {String} cid - ID of the comment to delete
* @param {function} callback - Takes only an Error object/null as the first argument
*/
2023-06-24 14:39:16 +08:00
CSteamSharedFile.prototype.deleteComment = function(cid, callback) {
this._community.deleteSharedFileComment(this.userID, this.id, cid, callback);
2023-05-14 21:26:55 +08:00
};
/**
* Favorites this sharedfile
* @param {function} callback - Takes only an Error object/null as the first argument
*/
2023-06-24 14:39:16 +08:00
CSteamSharedFile.prototype.favorite = function(callback) {
this._community.favoriteSharedFile(this.id, this.appID, callback);
2023-05-14 21:26:55 +08:00
};
/**
* Posts a comment to this sharedfile
* @param {String} message - Content of the comment to post
* @param {function} callback - Takes only an Error object/null as the first argument
*/
2023-06-24 14:39:16 +08:00
CSteamSharedFile.prototype.comment = function(message, callback) {
this._community.postSharedFileComment(this.owner, this.id, message, callback);
2023-05-14 21:26:55 +08:00
};
/**
* 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
*/
2023-06-24 14:39:16 +08:00
CSteamSharedFile.prototype.subscribe = function(callback) {
this._community.subscribeSharedFileComments(this.owner, this.id, callback);
2023-05-14 21:26:55 +08:00
};
/**
* Unfavorites this sharedfile
* @param {function} callback - Takes only an Error object/null as the first argument
*/
2023-06-24 14:39:16 +08:00
CSteamSharedFile.prototype.unfavorite = function(callback) {
this._community.unfavoriteSharedFile(this.id, this.appID, callback);
2023-05-14 21:26:55 +08:00
};
/**
* 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
*/
2023-06-24 14:39:16 +08:00
CSteamSharedFile.prototype.unsubscribe = function(callback) {
this._community.unsubscribeSharedFileComments(this.owner, this.id, callback);
2023-05-14 21:26:55 +08:00
};