mirror of
https://github.com/DoctorMcKay/node-steamcommunity.git
synced 2025-01-14 12:50:11 +08:00
Add sharedfile class with fully working scraper
This commit is contained in:
parent
c8642865a1
commit
9ec5dcd7b0
152
classes/CSteamSharedfile.js
Normal file
152
classes/CSteamSharedfile.js
Normal file
@ -0,0 +1,152 @@
|
||||
const Cheerio = require("cheerio");
|
||||
const SteamID = require("steamid");
|
||||
const SteamCommunity = require("../index.js");
|
||||
const steamIdResolver = require("steamid-resolver");
|
||||
const ESharedfileType = require("../resources/ESharedfileType.js");
|
||||
|
||||
|
||||
/**
|
||||
* Scrape a sharedfile's DOM to get all available information
|
||||
* @param {String} sid - ID of the sharedfile
|
||||
* @param {function} callback - First argument is null/Error, second is object containing all available information
|
||||
*/
|
||||
SteamCommunity.prototype.getSteamSharedfile = function(sid, callback) {
|
||||
// Construct object holding all the data we can scrape
|
||||
let sharedfile = {
|
||||
id: sid,
|
||||
type: null,
|
||||
appID: null,
|
||||
owner: null,
|
||||
fileSize: null,
|
||||
postDate: null,
|
||||
resolution: null,
|
||||
uniqueVisitorsCount: null,
|
||||
favoritesCount: null,
|
||||
upvoteCount: null
|
||||
}
|
||||
|
||||
|
||||
// Get DOM of sharedfile
|
||||
this.httpRequestGet(`https://steamcommunity.com/sharedfiles/filedetails/?id=${sid}`, (err, res, body) => {
|
||||
try {
|
||||
|
||||
/* --------------------- Preprocess output --------------------- */
|
||||
|
||||
// Load output into cheerio to make parsing easier
|
||||
let $ = Cheerio.load(body);
|
||||
|
||||
// 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();
|
||||
|
||||
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
|
||||
|
||||
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();
|
||||
|
||||
Object.keys(statsTable).forEach((e, i) => {
|
||||
if (isNaN(e)) return; // Ignore invalid entries
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
|
||||
/* --------------------- Find and map values --------------------- */
|
||||
|
||||
// Find appID in share button onclick event
|
||||
sharedfile.appID = Number($("#ShareItemBtn").attr()["onclick"].replace(`ShowSharePublishedFilePopup( '${sid}', '`, "").replace("' );", ""))
|
||||
|
||||
|
||||
// Find owner profile link, convert to steamID64 using steamIdResolver lib and create a SteamID object
|
||||
let ownerHref = $(".friendBlockLinkOverlay").attr()["href"];
|
||||
|
||||
steamIdResolver.customUrlToSteamID64(ownerHref, (err, steamID64) => { // Note: Callback will be called before this is done, takes around 1 sec to populate
|
||||
if (!err) sharedfile.owner = new SteamID(steamID64);
|
||||
});
|
||||
|
||||
|
||||
// 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
|
||||
|
||||
// Find postDate and convert to timestamp (Warning: Will get ugly)
|
||||
let posted = detailsStatsObj["Posted"].replace(/,|@/g, "").split(" "); // Remove comma behind month and @, the date & time separator. Split by space to get: Day, Month, Year (if not current) and time
|
||||
let months = { "Jan": "01", "Feb": "02", "Mar": "03", "Apr": "04", "May": "05", "Jun": "06", "Jul": "07", "Aug": "08", "Sep": "09", "Oct": "10", "Nov": "11", "Dec": "12" }; // Map all month abbreviations
|
||||
|
||||
if (posted[0].split(":")[0].length == 1) posted[0] = "0" + posted[0]; // Add zero if day is <10 to have a fixed length
|
||||
|
||||
posted[1] = months[posted[1]]; // Replace month abbreviation with corresponding Number
|
||||
|
||||
if (!posted[2]) posted[2] = new Date().getUTCFullYear().toString(); // Add current year if Steam did not list one
|
||||
else posted.splice(3, 1); // ...otherwise remove element 3 as it will be an empty String
|
||||
|
||||
// Convert AM/PM time to 24h format - Credit: https://stackoverflow.com/a/40197728 (Modified)
|
||||
if (posted[3].split(":")[0].length == 1) posted[3] = "0" + posted[3]; // Add zero if hour is <10 to have a fixed length
|
||||
|
||||
let time = posted[3].substring(0, 5);
|
||||
let modifier = posted[3].substring(5, 7);
|
||||
let [hours, minutes] = time.split(":");
|
||||
|
||||
if (hours === "12") hours = "00";
|
||||
if (modifier === "pm") hours = parseInt(hours, 10) + 12;
|
||||
|
||||
sharedfile.postDate = Date.parse(`${posted[2]}-${posted[1]}-${posted[0]}T${hours}:${minutes}:00.000Z`); // Construct Date String and parse it to get Unix timestamp
|
||||
|
||||
|
||||
// Find resolution if artwork or screenshot
|
||||
sharedfile.resolution = detailsStatsObj["Size"] || null;
|
||||
|
||||
|
||||
// Find uniqueVisitorsCount. We can't use ' || null' here as Number("0") casts to false
|
||||
if (statsTableObj["Unique Visitors"]) sharedfile.uniqueVisitorsCount = Number(statsTableObj["Unique Visitors"]);
|
||||
|
||||
|
||||
// Find favoritesCount. We can't use ' || null' here as Number("0") casts to false
|
||||
if (statsTableObj["Current Favorites"]) sharedfile.favoritesCount = Number(statsTableObj["Current Favorites"]);
|
||||
|
||||
|
||||
// Find upvoteCount. We can't use ' || null' here as Number("0") casts to false
|
||||
let upvoteCount = $("#VotesUpCountContainer > #VotesUpCount").text();
|
||||
if (upvoteCount) sharedfile.upvoteCount = Number(upvoteCount);
|
||||
|
||||
|
||||
// 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;
|
||||
|
||||
|
||||
callback(null, new CSteamSharedfile(this, sharedfile));
|
||||
|
||||
} catch (err) {
|
||||
callback(err, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function CSteamSharedfile(community, data) {
|
||||
this._community = community;
|
||||
|
||||
// Clone all the data we recieved
|
||||
Object.assign(this, data); // TODO: This is cleaner but might break IntelliSense
|
||||
|
||||
/* this.id = data.id;
|
||||
this.type = data.type;
|
||||
this.appID = data.appID;
|
||||
this.owner = data.owner;
|
||||
this.fileSize = data.fileSize;
|
||||
this.postDate = data.postDate;
|
||||
this.resolution = data.resolution;
|
||||
this.uniqueVisitorsCount = data.uniqueVisitorsCount;
|
||||
this.favoritesCount = data.favoritesCount;
|
||||
this.upvoteCount = data.upvoteCount; */
|
||||
}
|
@ -28,6 +28,7 @@
|
||||
"request": "^2.88.0",
|
||||
"steam-totp": "^1.5.0",
|
||||
"steamid": "^1.1.3",
|
||||
"steamid-resolver": "^1.2.3",
|
||||
"xml2js": "^0.4.22"
|
||||
},
|
||||
"engines": {
|
||||
|
Loading…
Reference in New Issue
Block a user