From cbdfa516cea90f541314166210153c950012d66b Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Thu, 14 Sep 2023 21:55:26 +0200
Subject: [PATCH 01/21] Add discussion class with data scraper
---
classes/CSteamDiscussion.js | 108 ++++++++++++++++++++++++++++++++++++
index.js | 1 +
2 files changed, 109 insertions(+)
create mode 100644 classes/CSteamDiscussion.js
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
new file mode 100644
index 0000000..f44fb07
--- /dev/null
+++ b/classes/CSteamDiscussion.js
@@ -0,0 +1,108 @@
+const Cheerio = require('cheerio');
+const SteamID = require('steamid');
+
+const SteamCommunity = require('../index.js');
+const Helpers = require('../components/helpers.js');
+
+
+/**
+ * Scrape a discussion's DOM to get all available information
+ * @param {string} url - SteamCommunity url pointing to the discussion to fetch
+ * @param {function} callback - First argument is null/Error, second is object containing all available information
+ */
+SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
+ // Construct object holding all the data we can scrape
+ let discussion = {
+ id: null,
+ appID: null,
+ forumID: null,
+ author: null,
+ postedDate: null,
+ title: null,
+ content: null,
+ commentsAmount: null // I originally wanted to fetch all comments by default but that would have been a lot of potentially unused data
+ };
+
+ // Get DOM of discussion
+ this.httpRequestGet(url, (err, res, body) => {
+ try {
+
+ /* --------------------- Preprocess output --------------------- */
+
+ // Load output into cheerio to make parsing easier
+ let $ = Cheerio.load(body);
+
+ // Get breadcrumbs once
+ let breadcrumbs = $(".forum_breadcrumbs").children();
+
+
+ /* --------------------- Find and map values --------------------- */
+
+ // Get discussionID from url
+ discussion.id = url.split("/")[url.split("/").length - 1];
+
+
+ // Get appID from breadcrumbs
+ let appIdHref = breadcrumbs[0].attribs["href"].split("/");
+
+ discussion.appID = appIdHref[appIdHref.length - 1];
+
+
+ // Get forumID from breadcrumbs
+ let forumIdHref = breadcrumbs[2].attribs["href"].split("/");
+
+ discussion.forumID = forumIdHref[forumIdHref.length - 2];
+
+
+ // Find postedDate and convert to timestamp
+ let posted = $(".topicstats > .topicstats_label:contains(\"Date Posted:\")").next().text()
+
+ discussion.postedDate = Helpers.decodeSteamTime(posted.trim());
+
+
+ // Find commentsAmount
+ discussion.commentsAmount = Number($(".topicstats > .topicstats_label:contains(\"Posts:\")").next().text());
+
+
+ // Get discussion title & content
+ discussion.title = $(".forum_op > .topic").text().trim();
+ discussion.content = $(".forum_op > .content").text().trim();
+
+
+ // Find author and convert to SteamID object
+ let authorLink = $(".authorline > .forum_op_author").attr("href");
+
+ Helpers.resolveVanityURL(authorLink, (err, data) => { // This request takes <1 sec
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ discussion.author = new SteamID(data.steamID);
+
+ // Make callback when ID was resolved as otherwise owner will always be null
+ callback(null, new CSteamDiscussion(this, discussion));
+ });
+
+ } catch (err) {
+ callback(err, null);
+ }
+ }, "steamcommunity");
+}
+
+
+/**
+ * Constructor - Creates a new Discussion object
+ * @class
+ * @param {SteamCommunity} community
+ * @param {{ id: string, appID: string, forumID: string, author: SteamID, postedDate: Object, title: string, content: string, commentsAmount: number }} data
+ */
+function CSteamDiscussion(community, data) {
+ /**
+ * @type {SteamCommunity}
+ */
+ this._community = community;
+
+ // Clone all the data we received
+ Object.assign(this, data);
+}
\ No newline at end of file
diff --git a/index.js b/index.js
index 0fc31ed..60fd8c7 100644
--- a/index.js
+++ b/index.js
@@ -590,6 +590,7 @@ require('./components/confirmations.js');
require('./components/help.js');
require('./classes/CMarketItem.js');
require('./classes/CMarketSearchResult.js');
+require('./classes/CSteamDiscussion.js');
require('./classes/CSteamGroup.js');
require('./classes/CSteamSharedFile.js');
require('./classes/CSteamUser.js');
From 5cce2e776682a6dafd78f9f33780a0175b607020 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Fri, 15 Sep 2023 10:44:35 +0200
Subject: [PATCH 02/21] Scrape comment id marked as answer
---
classes/CSteamDiscussion.js | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index f44fb07..56543a9 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -20,7 +20,8 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
postedDate: null,
title: null,
content: null,
- commentsAmount: null // I originally wanted to fetch all comments by default but that would have been a lot of potentially unused data
+ commentsAmount: null, // I originally wanted to fetch all comments by default but that would have been a lot of potentially unused data
+ answerCommentIndex: null
};
// Get DOM of discussion
@@ -69,6 +70,17 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
discussion.content = $(".forum_op > .content").text().trim();
+ // Find comment marked as answer
+ let hasAnswer = $(".commentthread_answer_bar")
+
+ if (hasAnswer.length != 0) {
+ let answerPermLink = hasAnswer.next().children(".forum_comment_permlink").text().trim();
+
+ // Convert comment id to number, remove hashtag and subtract by 1 to make it an index
+ discussion.answerCommentIndex = Number(answerPermLink.replace("#", "")) - 1;
+ }
+
+
// Find author and convert to SteamID object
let authorLink = $(".authorline > .forum_op_author").attr("href");
From ab658f240acf18ab6d40ef53cdbc4ab8f5913585 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sat, 16 Sep 2023 00:47:07 +0200
Subject: [PATCH 03/21] Add discussion comment scraping support
---
components/discussions.js | 119 ++++++++++++++++++++++++++++++++++++++
index.js | 1 +
2 files changed, 120 insertions(+)
create mode 100644 components/discussions.js
diff --git a/components/discussions.js b/components/discussions.js
new file mode 100644
index 0000000..6d60e2c
--- /dev/null
+++ b/components/discussions.js
@@ -0,0 +1,119 @@
+const Cheerio = require('cheerio');
+
+const SteamCommunity = require('../index.js');
+const Helpers = require('../components/helpers.js');
+
+
+/**
+ * Scrapes a range of comments from a Steam discussion
+ * @param {url} url - SteamCommunity url pointing to the discussion to fetch
+ * @param {number} startIndex - Index (0 based) of the first comment to fetch
+ * @param {number} endIndex - Index (0 based) of the last comment to fetch
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIndex, callback) {
+ this.httpRequestGet(url + "?l=en", async (err, res, body) => {
+
+ if (err) {
+ callback("Failed to load discussion: " + err, null);
+ return;
+ }
+
+
+ // Load output into cheerio to make parsing easier
+ let $ = Cheerio.load(body);
+
+ let paging = $(".forum_paging > .forum_paging_summary").children();
+
+ /**
+ * Stores every loaded page inside a Cheerio instance
+ * @type {{[key: number]: cheerio.Root}}
+ */
+ let pages = {
+ 0: $
+ };
+
+
+ // Determine amount of comments per page and total. Update endIndex if null to get all comments
+ let commentsPerPage = Number(paging[4].children[0].data);
+ let totalComments = Number(paging[5].children[0].data)
+
+ if (endIndex == null || endIndex > totalComments - 1) { // Make sure to check against null as the index 0 would cast to false
+ endIndex = totalComments - 1;
+ }
+
+
+ // Save all pages that need to be fetched in order to get the requested comments
+ let firstPage = Math.trunc(startIndex / commentsPerPage); // Index of the first page that needs to be fetched
+ let lastPage = Math.trunc(endIndex / commentsPerPage);
+ let promises = [];
+
+ for (let i = firstPage; i <= lastPage; i++) {
+ if (i == 0) continue; // First page is already in pages object
+
+ promises.push(new Promise((resolve) => {
+ setTimeout(() => { // Delay fetching a bit to reduce the risk of Steam blocking us
+
+ this.httpRequestGet(url + "?l=en&ctp=" + (i + 1), (err, res, body) => {
+ try {
+ pages[i] = Cheerio.load(body);
+ resolve();
+ } catch (err) {
+ return callback("Failed to load comments page: " + err, null);
+ }
+ }, "steamcommunity");
+
+ }, 250 * i);
+ }));
+ }
+
+ await Promise.all(promises); // Wait for all pages to be fetched
+
+
+ // Fill comments with content of all comments
+ let comments = [];
+
+ for (let i = startIndex; i <= endIndex; i++) {
+ let $ = pages[Math.trunc(i / commentsPerPage)];
+
+ let thisComment = $(`.forum_comment_permlink:contains("#${i + 1}")`).parent();
+
+ // Note: '>' inside the cheerio selectors didn't work here
+ let authorContainer = thisComment.children(".commentthread_comment_content").children(".commentthread_comment_author").children(".commentthread_author_link");
+ let commentContainer = thisComment.children(".commentthread_comment_content").children(".commentthread_comment_text");
+
+
+ // Prepare comment text
+ let commentText = "";
+
+ if (commentContainer.children(".bb_blockquote").length != 0) { // Check if comment contains quote
+ commentText += commentContainer.children(".bb_blockquote").children(".bb_quoteauthor").text() + "\n"; // Get quote header and add a proper newline
+
+ let quoteWithNewlines = commentContainer.children(".bb_blockquote").first().find("br").replaceWith("\n"); // Replace
's with newlines to get a proper output
+
+ commentText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text' }).text().trim(); // Get blockquote content without child content - https://stackoverflow.com/a/23956052
+
+ commentText += "\n\n-------\n\n"; // Add spacer
+ }
+
+ let quoteWithNewlines = commentContainer.first().find("br").replaceWith("\n"); // Replace
's with newlines to get a proper output
+
+ commentText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text' }).text().trim(); // Add comment content without child content - https://stackoverflow.com/a/23956052
+
+
+ comments.push({
+ index: i,
+ commentId: thisComment.attr("id").replace("comment_", ""),
+ commentLink: `${url}#${thisComment.attr("id").replace("comment_", "c")}`,
+ authorLink: authorContainer.attr("href"), // I did not call 'resolveVanityURL()' here and convert to SteamID to reduce the amount of potentially unused Steam pings
+ postedDate: Helpers.decodeSteamTime(authorContainer.children(".commentthread_comment_timestamp").text().trim()),
+ content: commentText.trim()
+ });
+ }
+
+
+ // Callback our result
+ callback(null, comments);
+
+ }, "steamcommunity");
+};
\ No newline at end of file
diff --git a/index.js b/index.js
index 60fd8c7..e8ce722 100644
--- a/index.js
+++ b/index.js
@@ -587,6 +587,7 @@ require('./components/inventoryhistory.js');
require('./components/webapi.js');
require('./components/twofactor.js');
require('./components/confirmations.js');
+require('./components/discussions.js');
require('./components/help.js');
require('./classes/CMarketItem.js');
require('./classes/CMarketSearchResult.js');
From bdb3b5b427f7786310bf935575da4b30670ad643 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sat, 16 Sep 2023 23:10:18 +0200
Subject: [PATCH 04/21] Add discussion subscribing support
---
components/discussions.js | 50 +++++++++++++++++++++++++++++++++++++++
1 file changed, 50 insertions(+)
diff --git a/components/discussions.js b/components/discussions.js
index 6d60e2c..6a0941f 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -116,4 +116,54 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
callback(null, comments);
}, "steamcommunity");
+};
+
+/**
+ * Subscribes to a discussion's comment section
+ * @param {String} topicOwner - ID of the topic owner
+ * @param {String} gidforum - GID of the discussion's forum
+ * @param {String} discussionId - ID of the discussion
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidforum, discussionId, callback) {
+ this.httpRequestPost({
+ "uri": `https://steamcommunity.com/comment/ForumTopic/subscribe/${topicOwner}/${gidforum}/`,
+ "form": {
+ "count": 15,
+ "sessionid": this.getSessionID(),
+ "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}',
+ "feature2": discussionId
+ }
+ }, function(err, response, body) { // eslint-disable-line
+ if (!callback) {
+ return;
+ }
+
+ callback(err);
+ }, "steamcommunity");
+};
+
+/**
+ * Unsubscribes from a discussion's comment section
+ * @param {String} topicOwner - ID of the topic owner
+ * @param {String} gidforum - GID of the discussion's forum
+ * @param {String} discussionId - ID of the discussion
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gidforum, discussionId, callback) {
+ this.httpRequestPost({
+ "uri": `https://steamcommunity.com/comment/ForumTopic/unsubscribe/${topicOwner}/${gidforum}/`,
+ "form": {
+ "count": 15,
+ "sessionid": this.getSessionID(),
+ "extended_data": '{}', // Unsubscribing does not require any data here
+ "feature2": discussionId
+ }
+ }, function(err, response, body) { // eslint-disable-line
+ if (!callback) {
+ return;
+ }
+
+ callback(err);
+ }, "steamcommunity");
};
\ No newline at end of file
From 80755d85921940b0e64a8071d8da76190f1d8f9c Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sat, 16 Sep 2023 23:23:37 +0200
Subject: [PATCH 05/21] Add discussion commenting support
---
components/discussions.js | 56 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
diff --git a/components/discussions.js b/components/discussions.js
index 6a0941f..25d23c1 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -118,6 +118,60 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
}, "steamcommunity");
};
+/**
+ * Posts a comment to a discussion
+ * @param {String} topicOwner - ID of the topic owner
+ * @param {String} gidforum - GID of the discussion's forum
+ * @param {String} discussionId - ID of the discussion
+ * @param {String} message - Content of the comment to post
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum, discussionId, message, callback) {
+ this.httpRequestPost({
+ "uri": `https://steamcommunity.com/comment/ForumTopic/post/${topicOwner}/${gidforum}/`,
+ "form": {
+ "comment": message,
+ "count": 15,
+ "sessionid": this.getSessionID(),
+ "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}', // This parameter is required, not sure about the specific settings
+ "feature2": discussionId
+ }
+ }, function(err, response, body) {
+ if (!callback) {
+ return;
+ }
+
+ callback(err);
+ }, "steamcommunity");
+};
+
+/**
+ * Deletes a comment from a discussion
+ * @param {String} topicOwner - ID of the topic owner
+ * @param {String} gidforum - GID of the discussion's forum
+ * @param {String} discussionId - ID of the discussion
+ * @param {String} gidcomment - ID of the comment to delete
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum, discussionId, gidcomment, callback) {
+ this.httpRequestPost({
+ "uri": `https://steamcommunity.com/comment/ForumTopic/delete/${topicOwner}/${gidforum}/`,
+ "form": {
+ "gidcomment": gidcomment,
+ "count": 15,
+ "sessionid": this.getSessionID(),
+ "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}', // This parameter is required, not sure about the specific settings
+ "feature2": discussionId
+ }
+ }, function(err, response, body) {
+ if (!callback) {
+ return;
+ }
+
+ callback(err);
+ }, "steamcommunity");
+};
+
/**
* Subscribes to a discussion's comment section
* @param {String} topicOwner - ID of the topic owner
@@ -131,7 +185,7 @@ SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidf
"form": {
"count": 15,
"sessionid": this.getSessionID(),
- "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}',
+ "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}', // This parameter is required, not sure about the specific settings
"feature2": discussionId
}
}, function(err, response, body) { // eslint-disable-line
From 3b8f92e2b682e2639d3c983a19c6fffe1962957f Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sat, 16 Sep 2023 23:53:44 +0200
Subject: [PATCH 06/21] Scrape gidforum & topicOwner
---
classes/CSteamDiscussion.js | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index 56543a9..4e7948e 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -1,7 +1,7 @@
const Cheerio = require('cheerio');
const SteamID = require('steamid');
-const SteamCommunity = require('../index.js');
+const SteamCommunity = require('../index.js');
const Helpers = require('../components/helpers.js');
@@ -16,6 +16,8 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
id: null,
appID: null,
forumID: null,
+ gidforum: null, // This is some id used as parameter 2 in post requests
+ topicOwner: null, // This is some id used as parameter 1 in post requests
author: null,
postedDate: null,
title: null,
@@ -55,6 +57,13 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
discussion.forumID = forumIdHref[forumIdHref.length - 2];
+ // Get gidforum and topicOwner. I'm not 100% sure what they are, they are however used for all post requests
+ let gids = $(".forum_paging > .forum_paging_controls").attr("id").split("_");
+
+ discussion.gidforum = gids[3];
+ discussion.topicOwner = gids[2];
+
+
// Find postedDate and convert to timestamp
let posted = $(".topicstats > .topicstats_label:contains(\"Date Posted:\")").next().text()
From 5c48d7b746dd6d6c839c75a7fe12f2e55be2a343 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sun, 17 Sep 2023 00:18:11 +0200
Subject: [PATCH 07/21] Add discussion object methods
---
classes/CSteamDiscussion.js | 40 ++++++++++++++++++++++++++++++++++++-
1 file changed, 39 insertions(+), 1 deletion(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index 4e7948e..f59997e 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -126,4 +126,42 @@ function CSteamDiscussion(community, data) {
// Clone all the data we received
Object.assign(this, data);
-}
\ No newline at end of file
+}
+
+
+/**
+ * Posts a comment to this discussion's comment section
+ * @param {String} message - Content of the comment to post
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+CSteamDiscussion.prototype.postComment = function(message, callback) {
+ this._community.postDiscussionComment(this.topicOwner, this.gidforum, this.id, message, callback);
+};
+
+
+/**
+ * Delete a comment from this discussion's comment section
+ * @param {String} gidcomment - ID of the comment to delete
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+CSteamDiscussion.prototype.deleteComment = function(gidcomment, callback) {
+ this._community.deleteDiscussionComment(this.topicOwner, this.gidforum, this.id, gidcomment, callback);
+};
+
+
+/**
+ * Subscribes to this discussion's comment section
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+CSteamDiscussion.prototype.subscribe = function(callback) {
+ this._community.subscribeDiscussionComments(this.topicOwner, this.gidforum, this.id, callback);
+};
+
+
+/**
+ * Unsubscribes from this discussion's comment section
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+CSteamDiscussion.prototype.unsubscribe = function(callback) {
+ this._community.unsubscribeDiscussionComments(this.topicOwner, this.gidforum, this.id, callback);
+};
From 660c9151f7ca9d8883b3d851d8737f5e5c4d3b0c Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sun, 17 Sep 2023 00:18:40 +0200
Subject: [PATCH 08/21] Get id more reliably and misc
---
classes/CSteamDiscussion.js | 7 ++-----
components/discussions.js | 6 +++---
2 files changed, 5 insertions(+), 8 deletions(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index f59997e..35072e1 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -41,10 +41,6 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
/* --------------------- Find and map values --------------------- */
- // Get discussionID from url
- discussion.id = url.split("/")[url.split("/").length - 1];
-
-
// Get appID from breadcrumbs
let appIdHref = breadcrumbs[0].attribs["href"].split("/");
@@ -57,9 +53,10 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
discussion.forumID = forumIdHref[forumIdHref.length - 2];
- // Get gidforum and topicOwner. I'm not 100% sure what they are, they are however used for all post requests
+ // Get id, gidforum and topicOwner. The first is used in the URL itself, the other two only in post requests
let gids = $(".forum_paging > .forum_paging_controls").attr("id").split("_");
+ discussion.id = gids[4];
discussion.gidforum = gids[3];
discussion.topicOwner = gids[2];
diff --git a/components/discussions.js b/components/discussions.js
index 25d23c1..3bf2108 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -133,7 +133,7 @@ SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum,
"comment": message,
"count": 15,
"sessionid": this.getSessionID(),
- "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}', // This parameter is required, not sure about the specific settings
+ "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
"feature2": discussionId
}
}, function(err, response, body) {
@@ -160,7 +160,7 @@ SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum
"gidcomment": gidcomment,
"count": 15,
"sessionid": this.getSessionID(),
- "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}', // This parameter is required, not sure about the specific settings
+ "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
"feature2": discussionId
}
}, function(err, response, body) {
@@ -185,7 +185,7 @@ SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidf
"form": {
"count": 15,
"sessionid": this.getSessionID(),
- "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1,"is_banned":0,"can_delete":0,"can_edit":0}}', // This parameter is required, not sure about the specific settings
+ "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
"feature2": discussionId
}
}, function(err, response, body) { // eslint-disable-line
From d5f9dc762234aebfb6c8faec3afb431db7dcd882 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sun, 17 Sep 2023 00:26:26 +0200
Subject: [PATCH 09/21] Add missing getComments() object method and fix
callback jsdoc
---
classes/CSteamDiscussion.js | 11 +++++++++++
components/discussions.js | 2 +-
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index 35072e1..4461322 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -126,6 +126,17 @@ function CSteamDiscussion(community, data) {
}
+/**
+ * Scrapes a range of comments from this discussion
+ * @param {number} startIndex - Index (0 based) of the first comment to fetch
+ * @param {number} endIndex - Index (0 based) of the last comment to fetch
+ * @param {function} callback - First argument is null/Error, second is array containing the requested comments
+ */
+CSteamDiscussion.prototype.getComments = function(startIndex, endIndex, callback) {
+ this._community.getDiscussionComments(`https://steamcommunity.com/app/${this.appID}/discussions/${this.forumID}/${this.id}`, startIndex, endIndex, callback);
+};
+
+
/**
* Posts a comment to this discussion's comment section
* @param {String} message - Content of the comment to post
diff --git a/components/discussions.js b/components/discussions.js
index 3bf2108..146dc8a 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -9,7 +9,7 @@ const Helpers = require('../components/helpers.js');
* @param {url} url - SteamCommunity url pointing to the discussion to fetch
* @param {number} startIndex - Index (0 based) of the first comment to fetch
* @param {number} endIndex - Index (0 based) of the last comment to fetch
- * @param {function} callback - Takes only an Error object/null as the first argument
+ * @param {function} callback - First argument is null/Error, second is array containing the requested comments
*/
SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIndex, callback) {
this.httpRequestGet(url + "?l=en", async (err, res, body) => {
From ee4c59f455d7087a8007a46c27e25eff996fc4b6 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sun, 17 Sep 2023 00:56:00 +0200
Subject: [PATCH 10/21] Fix first comment getting blockquote from another
comment
---
components/discussions.js | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/components/discussions.js b/components/discussions.js
index 146dc8a..12d0c14 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -77,13 +77,14 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
let $ = pages[Math.trunc(i / commentsPerPage)];
let thisComment = $(`.forum_comment_permlink:contains("#${i + 1}")`).parent();
+ let thisCommentID = thisComment.attr("id").replace("comment_", "");
// Note: '>' inside the cheerio selectors didn't work here
- let authorContainer = thisComment.children(".commentthread_comment_content").children(".commentthread_comment_author").children(".commentthread_author_link");
- let commentContainer = thisComment.children(".commentthread_comment_content").children(".commentthread_comment_text");
+ let authorContainer = thisComment.children(".commentthread_comment_content").children(".commentthread_comment_author").children(".commentthread_author_link");
+ let commentContainer = thisComment.children(".commentthread_comment_content").children(`#comment_content_${thisCommentID}`);
- // Prepare comment text
+ // Prepare comment text by formatting the blockquote if one exists first, then adding the actual content
let commentText = "";
if (commentContainer.children(".bb_blockquote").length != 0) { // Check if comment contains quote
@@ -103,8 +104,8 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
comments.push({
index: i,
- commentId: thisComment.attr("id").replace("comment_", ""),
- commentLink: `${url}#${thisComment.attr("id").replace("comment_", "c")}`,
+ commentId: thisCommentID,
+ commentLink: `${url}#c${thisCommentID}`,
authorLink: authorContainer.attr("href"), // I did not call 'resolveVanityURL()' here and convert to SteamID to reduce the amount of potentially unused Steam pings
postedDate: Helpers.decodeSteamTime(authorContainer.children(".commentthread_comment_timestamp").text().trim()),
content: commentText.trim()
From fd8a03bd38bfac24c8bff3907e8acf3a9c2bc7e5 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sun, 17 Sep 2023 22:05:34 +0200
Subject: [PATCH 11/21] Add type detection and support forum & group
discussions
---
classes/CSteamDiscussion.js | 29 ++++++++++++++++++++++++-----
resources/EDiscussionType.js | 13 +++++++++++++
2 files changed, 37 insertions(+), 5 deletions(-)
create mode 100644 resources/EDiscussionType.js
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index 4461322..f0e297c 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -4,6 +4,8 @@ const SteamID = require('steamid');
const SteamCommunity = require('../index.js');
const Helpers = require('../components/helpers.js');
+const EDiscussionType = require("../resources/EDiscussionType.js");
+
/**
* Scrape a discussion's DOM to get all available information
@@ -14,6 +16,7 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
// Construct object holding all the data we can scrape
let discussion = {
id: null,
+ type: null,
appID: null,
forumID: null,
gidforum: null, // This is some id used as parameter 2 in post requests
@@ -38,17 +41,33 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
// Get breadcrumbs once
let breadcrumbs = $(".forum_breadcrumbs").children();
+ if (breadcrumbs.length == 0) breadcrumbs = $(".group_breadcrumbs").children();
+
/* --------------------- Find and map values --------------------- */
- // Get appID from breadcrumbs
- let appIdHref = breadcrumbs[0].attribs["href"].split("/");
+ // Determine type from URL as some checks will deviate, depending on the type
+ if (url.includes("steamcommunity.com/discussions/forum")) discussion.type = EDiscussionType.Forum;
+ if (/steamcommunity.com\/app\/.+\/discussions/g.test(url)) discussion.type = EDiscussionType.App;
+ if (/steamcommunity.com\/groups\/.+\/discussions/g.test(url)) discussion.type = EDiscussionType.Group;
- discussion.appID = appIdHref[appIdHref.length - 1];
+
+ // Get appID from breadcrumbs if this discussion is associated to one
+ if (discussion.type == EDiscussionType.App) {
+ let appIdHref = breadcrumbs[0].attribs["href"].split("/");
+
+ discussion.appID = appIdHref[appIdHref.length - 1];
+ }
// Get forumID from breadcrumbs
- let forumIdHref = breadcrumbs[2].attribs["href"].split("/");
+ let forumIdHref;
+
+ if (discussion.type == EDiscussionType.Group) { // Groups have an extra breadcrumb so we need to shift by 2
+ forumIdHref = breadcrumbs[4].attribs["href"].split("/");
+ } else {
+ forumIdHref = breadcrumbs[2].attribs["href"].split("/");
+ }
discussion.forumID = forumIdHref[forumIdHref.length - 2];
@@ -62,7 +81,7 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
// Find postedDate and convert to timestamp
- let posted = $(".topicstats > .topicstats_label:contains(\"Date Posted:\")").next().text()
+ let posted = $(".topicstats > .topicstats_label:contains(\"Date Posted:\")").next().text();
discussion.postedDate = Helpers.decodeSteamTime(posted.trim());
diff --git a/resources/EDiscussionType.js b/resources/EDiscussionType.js
new file mode 100644
index 0000000..b6c28bb
--- /dev/null
+++ b/resources/EDiscussionType.js
@@ -0,0 +1,13 @@
+/**
+ * @enum EDiscussionType
+ */
+module.exports = {
+ "Forum": 0,
+ "App": 1,
+ "Group": 2,
+
+ // Value-to-name mapping for convenience
+ "0": "Forum",
+ "1": "App",
+ "2": "Group"
+};
\ No newline at end of file
From 4e9155c8989f535d3b1cd51bc3c601e8a4343cbc Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sun, 17 Sep 2023 22:48:38 +0200
Subject: [PATCH 12/21] Add setDiscussionCommentsPerPage()
---
components/discussions.js | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/components/discussions.js b/components/discussions.js
index 12d0c14..afd5290 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -219,6 +219,30 @@ SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gi
return;
}
+ callback(err);
+ }, "steamcommunity");
+};
+
+/**
+ * Sets an amount of comments per page
+ * @param {String} value - 15, 30 or 50
+ * @param {function} callback - Takes only an Error object/null as the first argument
+ */
+SteamCommunity.prototype.setDiscussionCommentsPerPage = function(value, callback) {
+ if (!["15", "30", "50"].includes(value)) value = "50"; // Check for invalid setting
+
+ this.httpRequestPost({
+ "uri": `https://steamcommunity.com/forum/0/0/setpreference`,
+ "form": {
+ "preference": "topicrepliesperpage",
+ "value": value,
+ "sessionid": this.getSessionID(),
+ }
+ }, function(err, response, body) { // eslint-disable-line
+ if (!callback) {
+ return;
+ }
+
callback(err);
}, "steamcommunity");
};
\ No newline at end of file
From a5a3c8edb5165b838f68b2a5fa821ddec26571a2 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Mon, 18 Sep 2023 14:07:31 +0200
Subject: [PATCH 13/21] Add support for displaying multiple nested quotes
---
classes/CSteamDiscussion.js | 2 +-
components/discussions.js | 28 ++++++++++++++++++++++------
2 files changed, 23 insertions(+), 7 deletions(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index f0e297c..a1dd872 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -38,7 +38,7 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
// Load output into cheerio to make parsing easier
let $ = Cheerio.load(body);
- // Get breadcrumbs once
+ // Get breadcrumbs once. Depending on the type of discussion, it either uses "forum" or "group" breadcrumbs
let breadcrumbs = $(".forum_breadcrumbs").children();
if (breadcrumbs.length == 0) breadcrumbs = $(".group_breadcrumbs").children();
diff --git a/components/discussions.js b/components/discussions.js
index afd5290..7fed346 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -84,17 +84,33 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
let commentContainer = thisComment.children(".commentthread_comment_content").children(`#comment_content_${thisCommentID}`);
- // Prepare comment text by formatting the blockquote if one exists first, then adding the actual content
+ // Prepare comment text by finding all existing blockquotes, formatting them and adding them infront each other. Afterwards handle the text itself
let commentText = "";
+ let blockQuoteSelector = ".bb_blockquote";
+ let children = commentContainer.children(blockQuoteSelector);
- if (commentContainer.children(".bb_blockquote").length != 0) { // Check if comment contains quote
- commentText += commentContainer.children(".bb_blockquote").children(".bb_quoteauthor").text() + "\n"; // Get quote header and add a proper newline
+ for (let i = 0; i < 10; i++) { // I'm not sure how I could dynamically check the amount of nested blockquotes. 10 is prob already too much to stay readable
+ if (children.length > 0) {
+ let thisQuoteText = "";
- let quoteWithNewlines = commentContainer.children(".bb_blockquote").first().find("br").replaceWith("\n"); // Replace
's with newlines to get a proper output
+ thisQuoteText += children.children(".bb_quoteauthor").text() + "\n"; // Get quote header and add a proper newline
- commentText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text' }).text().trim(); // Get blockquote content without child content - https://stackoverflow.com/a/23956052
+ // Replace
's with newlines to get a proper output
+ let quoteWithNewlines = children.first().find("br").replaceWith("\n");
- commentText += "\n\n-------\n\n"; // Add spacer
+ thisQuoteText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text' }).text().trim(); // Get blockquote content without child content - https://stackoverflow.com/a/23956052
+ if (i > 0) thisQuoteText += "\n-------\n"; // Add spacer
+
+ commentText = thisQuoteText + commentText; // Concat quoteText to the start of commentText as the most nested quote is the first one inside the comment chain itself
+
+ // Go one level deeper
+ children = children.children(blockQuoteSelector);
+
+ } else {
+
+ commentText += "\n\n-------\n\n"; // Add spacer
+ break;
+ }
}
let quoteWithNewlines = commentContainer.first().find("br").replaceWith("\n"); // Replace
's with newlines to get a proper output
From eddc1d12751711e3ae8fd45eb751e4a0fd106ef9 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Thu, 28 Sep 2023 13:58:58 +0200
Subject: [PATCH 14/21] Fix checking for a rejected request
---
components/discussions.js | 99 ++++++++++++++++++++++++++++++++-------
1 file changed, 81 insertions(+), 18 deletions(-)
diff --git a/components/discussions.js b/components/discussions.js
index 7fed346..fe3bcb9 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -151,14 +151,25 @@ SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum,
"count": 15,
"sessionid": this.getSessionID(),
"extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- "feature2": discussionId
- }
+ "feature2": discussionId,
+ "json": 1
+ },
+ "json": true
}, function(err, response, body) {
if (!callback) {
return;
}
- callback(err);
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ if (body.success) {
+ callback(null);
+ } else {
+ callback(new Error(body.error));
+ }
}, "steamcommunity");
};
@@ -178,14 +189,25 @@ SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum
"count": 15,
"sessionid": this.getSessionID(),
"extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- "feature2": discussionId
- }
- }, function(err, response, body) {
+ "feature2": discussionId,
+ "json": 1
+ },
+ "json": true
+ }, function(err, response, body) { // Steam does not seem to return any errors here even when trying to delete a non-existing comment but let's check the response anyway
if (!callback) {
return;
}
- callback(err);
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ if (body.success) {
+ callback(null);
+ } else {
+ callback(new Error(body.error));
+ }
}, "steamcommunity");
};
@@ -203,14 +225,28 @@ SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidf
"count": 15,
"sessionid": this.getSessionID(),
"extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- "feature2": discussionId
- }
- }, function(err, response, body) { // eslint-disable-line
+ "feature2": discussionId,
+ "json": 1
+ },
+ "json": true
+ }, function(err, response, body) {
if (!callback) {
return;
}
- callback(err);
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ if (body.success && body.success != SteamCommunity.EResult.OK) {
+ let err = new Error(body.message || SteamCommunity.EResult[body.success]);
+ err.eresult = err.code = body.success;
+ callback(err);
+ return;
+ }
+
+ callback(null);
}, "steamcommunity");
};
@@ -228,14 +264,28 @@ SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gi
"count": 15,
"sessionid": this.getSessionID(),
"extended_data": '{}', // Unsubscribing does not require any data here
- "feature2": discussionId
- }
- }, function(err, response, body) { // eslint-disable-line
+ "feature2": discussionId,
+ "json": 1
+ },
+ "json": true
+ }, function(err, response, body) {
if (!callback) {
return;
}
- callback(err);
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ if (body.success && body.success != SteamCommunity.EResult.OK) {
+ let err = new Error(body.message || SteamCommunity.EResult[body.success]);
+ err.eresult = err.code = body.success;
+ callback(err);
+ return;
+ }
+
+ callback(null);
}, "steamcommunity");
};
@@ -253,12 +303,25 @@ SteamCommunity.prototype.setDiscussionCommentsPerPage = function(value, callback
"preference": "topicrepliesperpage",
"value": value,
"sessionid": this.getSessionID(),
- }
- }, function(err, response, body) { // eslint-disable-line
+ },
+ "json": true
+ }, function(err, response, body) { // Steam does not seem to return any errors for this request
if (!callback) {
return;
}
- callback(err);
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ if (body.success && body.success != SteamCommunity.EResult.OK) {
+ let err = new Error(body.message || SteamCommunity.EResult[body.success]);
+ err.eresult = err.code = body.success;
+ callback(err);
+ return;
+ }
+
+ callback(null);
}, "steamcommunity");
};
\ No newline at end of file
From a4eb96452d1edcdd69ba04372337bb36004ffbc9 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sun, 1 Oct 2023 22:26:51 +0200
Subject: [PATCH 15/21] Use eresultError() helper
---
components/discussions.js | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/components/discussions.js b/components/discussions.js
index fe3bcb9..a892c06 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -1,7 +1,7 @@
const Cheerio = require('cheerio');
const SteamCommunity = require('../index.js');
-const Helpers = require('../components/helpers.js');
+const Helpers = require('./helpers.js');
/**
@@ -240,9 +240,7 @@ SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidf
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
- let err = new Error(body.message || SteamCommunity.EResult[body.success]);
- err.eresult = err.code = body.success;
- callback(err);
+ callback(Helpers.eresultError(body.success));
return;
}
@@ -279,9 +277,7 @@ SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gi
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
- let err = new Error(body.message || SteamCommunity.EResult[body.success]);
- err.eresult = err.code = body.success;
- callback(err);
+ callback(Helpers.eresultError(body.success));
return;
}
@@ -316,9 +312,7 @@ SteamCommunity.prototype.setDiscussionCommentsPerPage = function(value, callback
}
if (body.success && body.success != SteamCommunity.EResult.OK) {
- let err = new Error(body.message || SteamCommunity.EResult[body.success]);
- err.eresult = err.code = body.success;
- callback(err);
+ callback(Helpers.eresultError(body.success));
return;
}
From d46b7c88042b5530f3e5ae00084e1dd07545a4d9 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Mon, 2 Oct 2023 15:01:12 +0200
Subject: [PATCH 16/21] Buff eslint's happiness by 80%
---
classes/CSteamDiscussion.js | 36 ++++-----
components/discussions.js | 142 +++++++++++++++++------------------
resources/EDiscussionType.js | 12 +--
3 files changed, 95 insertions(+), 95 deletions(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index a1dd872..abfcfb3 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -4,7 +4,7 @@ const SteamID = require('steamid');
const SteamCommunity = require('../index.js');
const Helpers = require('../components/helpers.js');
-const EDiscussionType = require("../resources/EDiscussionType.js");
+const EDiscussionType = require('../resources/EDiscussionType.js');
/**
@@ -39,22 +39,22 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
let $ = Cheerio.load(body);
// Get breadcrumbs once. Depending on the type of discussion, it either uses "forum" or "group" breadcrumbs
- let breadcrumbs = $(".forum_breadcrumbs").children();
+ let breadcrumbs = $('.forum_breadcrumbs').children();
- if (breadcrumbs.length == 0) breadcrumbs = $(".group_breadcrumbs").children();
+ if (breadcrumbs.length == 0) breadcrumbs = $('.group_breadcrumbs').children();
/* --------------------- Find and map values --------------------- */
// Determine type from URL as some checks will deviate, depending on the type
- if (url.includes("steamcommunity.com/discussions/forum")) discussion.type = EDiscussionType.Forum;
+ if (url.includes('steamcommunity.com/discussions/forum')) discussion.type = EDiscussionType.Forum;
if (/steamcommunity.com\/app\/.+\/discussions/g.test(url)) discussion.type = EDiscussionType.App;
if (/steamcommunity.com\/groups\/.+\/discussions/g.test(url)) discussion.type = EDiscussionType.Group;
// Get appID from breadcrumbs if this discussion is associated to one
if (discussion.type == EDiscussionType.App) {
- let appIdHref = breadcrumbs[0].attribs["href"].split("/");
+ let appIdHref = breadcrumbs[0].attribs.href.split('/');
discussion.appID = appIdHref[appIdHref.length - 1];
}
@@ -64,16 +64,16 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
let forumIdHref;
if (discussion.type == EDiscussionType.Group) { // Groups have an extra breadcrumb so we need to shift by 2
- forumIdHref = breadcrumbs[4].attribs["href"].split("/");
+ forumIdHref = breadcrumbs[4].attribs.href.split('/');
} else {
- forumIdHref = breadcrumbs[2].attribs["href"].split("/");
+ forumIdHref = breadcrumbs[2].attribs.href.split('/');
}
discussion.forumID = forumIdHref[forumIdHref.length - 2];
// Get id, gidforum and topicOwner. The first is used in the URL itself, the other two only in post requests
- let gids = $(".forum_paging > .forum_paging_controls").attr("id").split("_");
+ let gids = $('.forum_paging > .forum_paging_controls').attr('id').split('_');
discussion.id = gids[4];
discussion.gidforum = gids[3];
@@ -81,33 +81,33 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
// Find postedDate and convert to timestamp
- let posted = $(".topicstats > .topicstats_label:contains(\"Date Posted:\")").next().text();
+ let posted = $('.topicstats > .topicstats_label:contains("Date Posted:")').next().text();
discussion.postedDate = Helpers.decodeSteamTime(posted.trim());
// Find commentsAmount
- discussion.commentsAmount = Number($(".topicstats > .topicstats_label:contains(\"Posts:\")").next().text());
+ discussion.commentsAmount = Number($('.topicstats > .topicstats_label:contains("Posts:")').next().text());
// Get discussion title & content
- discussion.title = $(".forum_op > .topic").text().trim();
- discussion.content = $(".forum_op > .content").text().trim();
+ discussion.title = $('.forum_op > .topic').text().trim();
+ discussion.content = $('.forum_op > .content').text().trim();
// Find comment marked as answer
- let hasAnswer = $(".commentthread_answer_bar")
+ let hasAnswer = $('.commentthread_answer_bar');
if (hasAnswer.length != 0) {
- let answerPermLink = hasAnswer.next().children(".forum_comment_permlink").text().trim();
+ let answerPermLink = hasAnswer.next().children('.forum_comment_permlink').text().trim();
// Convert comment id to number, remove hashtag and subtract by 1 to make it an index
- discussion.answerCommentIndex = Number(answerPermLink.replace("#", "")) - 1;
+ discussion.answerCommentIndex = Number(answerPermLink.replace('#', '')) - 1;
}
// Find author and convert to SteamID object
- let authorLink = $(".authorline > .forum_op_author").attr("href");
+ let authorLink = $('.authorline > .forum_op_author').attr('href');
Helpers.resolveVanityURL(authorLink, (err, data) => { // This request takes <1 sec
if (err) {
@@ -124,8 +124,8 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
} catch (err) {
callback(err, null);
}
- }, "steamcommunity");
-}
+ }, 'steamcommunity');
+};
/**
diff --git a/components/discussions.js b/components/discussions.js
index a892c06..43eeadb 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -12,10 +12,10 @@ const Helpers = require('./helpers.js');
* @param {function} callback - First argument is null/Error, second is array containing the requested comments
*/
SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIndex, callback) {
- this.httpRequestGet(url + "?l=en", async (err, res, body) => {
+ this.httpRequestGet(url + '?l=en', async (err, res, body) => {
if (err) {
- callback("Failed to load discussion: " + err, null);
+ callback('Failed to load discussion: ' + err, null);
return;
}
@@ -23,20 +23,20 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
// Load output into cheerio to make parsing easier
let $ = Cheerio.load(body);
- let paging = $(".forum_paging > .forum_paging_summary").children();
+ let paging = $('.forum_paging > .forum_paging_summary').children();
/**
* Stores every loaded page inside a Cheerio instance
* @type {{[key: number]: cheerio.Root}}
*/
- let pages = {
+ let pages = {
0: $
};
// Determine amount of comments per page and total. Update endIndex if null to get all comments
let commentsPerPage = Number(paging[4].children[0].data);
- let totalComments = Number(paging[5].children[0].data)
+ let totalComments = Number(paging[5].children[0].data);
if (endIndex == null || endIndex > totalComments - 1) { // Make sure to check against null as the index 0 would cast to false
endIndex = totalComments - 1;
@@ -54,14 +54,14 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
promises.push(new Promise((resolve) => {
setTimeout(() => { // Delay fetching a bit to reduce the risk of Steam blocking us
- this.httpRequestGet(url + "?l=en&ctp=" + (i + 1), (err, res, body) => {
+ this.httpRequestGet(url + '?l=en&ctp=' + (i + 1), (err, res, body) => {
try {
pages[i] = Cheerio.load(body);
resolve();
} catch (err) {
- return callback("Failed to load comments page: " + err, null);
+ return callback('Failed to load comments page: ' + err, null);
}
- }, "steamcommunity");
+ }, 'steamcommunity');
}, 250 * i);
}));
@@ -77,29 +77,29 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
let $ = pages[Math.trunc(i / commentsPerPage)];
let thisComment = $(`.forum_comment_permlink:contains("#${i + 1}")`).parent();
- let thisCommentID = thisComment.attr("id").replace("comment_", "");
+ let thisCommentID = thisComment.attr('id').replace('comment_', '');
// Note: '>' inside the cheerio selectors didn't work here
- let authorContainer = thisComment.children(".commentthread_comment_content").children(".commentthread_comment_author").children(".commentthread_author_link");
- let commentContainer = thisComment.children(".commentthread_comment_content").children(`#comment_content_${thisCommentID}`);
+ let authorContainer = thisComment.children('.commentthread_comment_content').children('.commentthread_comment_author').children('.commentthread_author_link');
+ let commentContainer = thisComment.children('.commentthread_comment_content').children(`#comment_content_${thisCommentID}`);
// Prepare comment text by finding all existing blockquotes, formatting them and adding them infront each other. Afterwards handle the text itself
- let commentText = "";
- let blockQuoteSelector = ".bb_blockquote";
+ let commentText = '';
+ let blockQuoteSelector = '.bb_blockquote';
let children = commentContainer.children(blockQuoteSelector);
for (let i = 0; i < 10; i++) { // I'm not sure how I could dynamically check the amount of nested blockquotes. 10 is prob already too much to stay readable
if (children.length > 0) {
- let thisQuoteText = "";
+ let thisQuoteText = '';
- thisQuoteText += children.children(".bb_quoteauthor").text() + "\n"; // Get quote header and add a proper newline
+ thisQuoteText += children.children('.bb_quoteauthor').text() + '\n'; // Get quote header and add a proper newline
// Replace
's with newlines to get a proper output
- let quoteWithNewlines = children.first().find("br").replaceWith("\n");
+ let quoteWithNewlines = children.first().find('br').replaceWith('\n');
- thisQuoteText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text' }).text().trim(); // Get blockquote content without child content - https://stackoverflow.com/a/23956052
- if (i > 0) thisQuoteText += "\n-------\n"; // Add spacer
+ thisQuoteText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text'; }).text().trim(); // Get blockquote content without child content - https://stackoverflow.com/a/23956052
+ if (i > 0) thisQuoteText += '\n-------\n'; // Add spacer
commentText = thisQuoteText + commentText; // Concat quoteText to the start of commentText as the most nested quote is the first one inside the comment chain itself
@@ -108,31 +108,31 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
} else {
- commentText += "\n\n-------\n\n"; // Add spacer
+ commentText += '\n\n-------\n\n'; // Add spacer
break;
}
}
- let quoteWithNewlines = commentContainer.first().find("br").replaceWith("\n"); // Replace
's with newlines to get a proper output
+ let quoteWithNewlines = commentContainer.first().find('br').replaceWith('\n'); // Replace
's with newlines to get a proper output
- commentText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text' }).text().trim(); // Add comment content without child content - https://stackoverflow.com/a/23956052
+ commentText += quoteWithNewlines.end().contents().filter(function() { return this.type === 'text'; }).text().trim(); // Add comment content without child content - https://stackoverflow.com/a/23956052
comments.push({
index: i,
commentId: thisCommentID,
commentLink: `${url}#c${thisCommentID}`,
- authorLink: authorContainer.attr("href"), // I did not call 'resolveVanityURL()' here and convert to SteamID to reduce the amount of potentially unused Steam pings
- postedDate: Helpers.decodeSteamTime(authorContainer.children(".commentthread_comment_timestamp").text().trim()),
+ authorLink: authorContainer.attr('href'), // I did not call 'resolveVanityURL()' here and convert to SteamID to reduce the amount of potentially unused Steam pings
+ postedDate: Helpers.decodeSteamTime(authorContainer.children('.commentthread_comment_timestamp').text().trim()),
content: commentText.trim()
});
}
-
+
// Callback our result
callback(null, comments);
- }, "steamcommunity");
+ }, 'steamcommunity');
};
/**
@@ -145,16 +145,16 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
*/
SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum, discussionId, message, callback) {
this.httpRequestPost({
- "uri": `https://steamcommunity.com/comment/ForumTopic/post/${topicOwner}/${gidforum}/`,
- "form": {
- "comment": message,
- "count": 15,
- "sessionid": this.getSessionID(),
- "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- "feature2": discussionId,
- "json": 1
+ uri: `https://steamcommunity.com/comment/ForumTopic/post/${topicOwner}/${gidforum}/`,
+ form: {
+ comment: message,
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
+ feature2: discussionId,
+ json: 1
},
- "json": true
+ json: true
}, function(err, response, body) {
if (!callback) {
return;
@@ -170,7 +170,7 @@ SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum,
} else {
callback(new Error(body.error));
}
- }, "steamcommunity");
+ }, 'steamcommunity');
};
/**
@@ -183,16 +183,16 @@ SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum,
*/
SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum, discussionId, gidcomment, callback) {
this.httpRequestPost({
- "uri": `https://steamcommunity.com/comment/ForumTopic/delete/${topicOwner}/${gidforum}/`,
- "form": {
- "gidcomment": gidcomment,
- "count": 15,
- "sessionid": this.getSessionID(),
- "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- "feature2": discussionId,
- "json": 1
+ uri: `https://steamcommunity.com/comment/ForumTopic/delete/${topicOwner}/${gidforum}/`,
+ form: {
+ gidcomment: gidcomment,
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
+ feature2: discussionId,
+ json: 1
},
- "json": true
+ json: true
}, function(err, response, body) { // Steam does not seem to return any errors here even when trying to delete a non-existing comment but let's check the response anyway
if (!callback) {
return;
@@ -208,7 +208,7 @@ SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum
} else {
callback(new Error(body.error));
}
- }, "steamcommunity");
+ }, 'steamcommunity');
};
/**
@@ -220,15 +220,15 @@ SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum
*/
SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidforum, discussionId, callback) {
this.httpRequestPost({
- "uri": `https://steamcommunity.com/comment/ForumTopic/subscribe/${topicOwner}/${gidforum}/`,
- "form": {
- "count": 15,
- "sessionid": this.getSessionID(),
- "extended_data": '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- "feature2": discussionId,
- "json": 1
+ uri: `https://steamcommunity.com/comment/ForumTopic/subscribe/${topicOwner}/${gidforum}/`,
+ form: {
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
+ feature2: discussionId,
+ json: 1
},
- "json": true
+ json: true
}, function(err, response, body) {
if (!callback) {
return;
@@ -245,7 +245,7 @@ SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidf
}
callback(null);
- }, "steamcommunity");
+ }, 'steamcommunity');
};
/**
@@ -257,15 +257,15 @@ SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidf
*/
SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gidforum, discussionId, callback) {
this.httpRequestPost({
- "uri": `https://steamcommunity.com/comment/ForumTopic/unsubscribe/${topicOwner}/${gidforum}/`,
- "form": {
- "count": 15,
- "sessionid": this.getSessionID(),
- "extended_data": '{}', // Unsubscribing does not require any data here
- "feature2": discussionId,
- "json": 1
+ uri: `https://steamcommunity.com/comment/ForumTopic/unsubscribe/${topicOwner}/${gidforum}/`,
+ form: {
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{}', // Unsubscribing does not require any data here
+ feature2: discussionId,
+ json: 1
},
- "json": true
+ json: true
}, function(err, response, body) {
if (!callback) {
return;
@@ -282,7 +282,7 @@ SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gi
}
callback(null);
- }, "steamcommunity");
+ }, 'steamcommunity');
};
/**
@@ -291,16 +291,16 @@ SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gi
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.setDiscussionCommentsPerPage = function(value, callback) {
- if (!["15", "30", "50"].includes(value)) value = "50"; // Check for invalid setting
+ if (!['15', '30', '50'].includes(value)) value = '50'; // Check for invalid setting
this.httpRequestPost({
- "uri": `https://steamcommunity.com/forum/0/0/setpreference`,
- "form": {
- "preference": "topicrepliesperpage",
- "value": value,
- "sessionid": this.getSessionID(),
+ uri: 'https://steamcommunity.com/forum/0/0/setpreference',
+ form: {
+ preference: 'topicrepliesperpage',
+ value: value,
+ sessionid: this.getSessionID(),
},
- "json": true
+ json: true
}, function(err, response, body) { // Steam does not seem to return any errors for this request
if (!callback) {
return;
@@ -317,5 +317,5 @@ SteamCommunity.prototype.setDiscussionCommentsPerPage = function(value, callback
}
callback(null);
- }, "steamcommunity");
+ }, 'steamcommunity');
};
\ No newline at end of file
diff --git a/resources/EDiscussionType.js b/resources/EDiscussionType.js
index b6c28bb..99bcd60 100644
--- a/resources/EDiscussionType.js
+++ b/resources/EDiscussionType.js
@@ -2,12 +2,12 @@
* @enum EDiscussionType
*/
module.exports = {
- "Forum": 0,
- "App": 1,
- "Group": 2,
+ Forum: 0,
+ App: 1,
+ Group: 2,
// Value-to-name mapping for convenience
- "0": "Forum",
- "1": "App",
- "2": "Group"
+ 0: 'Forum',
+ 1: 'App',
+ 2: 'Group'
};
\ No newline at end of file
From 64eaf1b09272d1072b73518f327384dfe7ce22ab Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Mon, 2 Oct 2023 16:09:10 +0200
Subject: [PATCH 17/21] Return 'Discussion Not Found' error when breadcrumbs
are missing to avoid unhandled exceptions
---
classes/CSteamDiscussion.js | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index abfcfb3..8168b55 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -31,6 +31,11 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
// Get DOM of discussion
this.httpRequestGet(url, (err, res, body) => {
+ if (err) {
+ callback(err);
+ return;
+ }
+
try {
/* --------------------- Preprocess output --------------------- */
@@ -43,6 +48,11 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
if (breadcrumbs.length == 0) breadcrumbs = $('.group_breadcrumbs').children();
+ // Steam redirects us to the forum page if the discussion does not exist which we can detect by missing breadcrumbs
+ if (!breadcrumbs[0]) {
+ callback(new Error('Discussion not found'));
+ return;
+ }
/* --------------------- Find and map values --------------------- */
From e3dbb2c4935cf702231aebbda3e7ce8a2ac24b11 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Mon, 2 Oct 2023 20:12:40 +0200
Subject: [PATCH 18/21] Rename discussion postComment() -> comment()
---
classes/CSteamDiscussion.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index 8168b55..cefee03 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -171,7 +171,7 @@ CSteamDiscussion.prototype.getComments = function(startIndex, endIndex, callback
* @param {String} message - Content of the comment to post
* @param {function} callback - Takes only an Error object/null as the first argument
*/
-CSteamDiscussion.prototype.postComment = function(message, callback) {
+CSteamDiscussion.prototype.comment = function(message, callback) {
this._community.postDiscussionComment(this.topicOwner, this.gidforum, this.id, message, callback);
};
From 983ae0fbb6234842b014645102a77301c64e068e Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Mon, 2 Oct 2023 20:21:15 +0200
Subject: [PATCH 19/21] Update httpRequest helper usage to v4
---
classes/CSteamDiscussion.js | 27 ++--
components/discussions.js | 253 ++++++++++++++++--------------------
2 files changed, 129 insertions(+), 151 deletions(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index cefee03..18082ac 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -1,5 +1,6 @@
const Cheerio = require('cheerio');
const SteamID = require('steamid');
+const StdLib = require('@doctormckay/stdlib');
const SteamCommunity = require('../index.js');
const Helpers = require('../components/helpers.js');
@@ -30,18 +31,20 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
};
// Get DOM of discussion
- this.httpRequestGet(url, (err, res, body) => {
- if (err) {
- callback(err);
- return;
- }
+ return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
+ let result = await this.httpRequest({
+ method: 'GET',
+ url: url,
+ source: 'steamcommunity'
+ });
+
try {
/* --------------------- Preprocess output --------------------- */
// Load output into cheerio to make parsing easier
- let $ = Cheerio.load(body);
+ let $ = Cheerio.load(result.textBody);
// Get breadcrumbs once. Depending on the type of discussion, it either uses "forum" or "group" breadcrumbs
let breadcrumbs = $('.forum_breadcrumbs').children();
@@ -50,7 +53,7 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
// Steam redirects us to the forum page if the discussion does not exist which we can detect by missing breadcrumbs
if (!breadcrumbs[0]) {
- callback(new Error('Discussion not found'));
+ reject(new Error('Discussion not found'));
return;
}
@@ -121,20 +124,20 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
Helpers.resolveVanityURL(authorLink, (err, data) => { // This request takes <1 sec
if (err) {
- callback(err);
+ reject(err);
return;
}
discussion.author = new SteamID(data.steamID);
- // Make callback when ID was resolved as otherwise owner will always be null
- callback(null, new CSteamDiscussion(this, discussion));
+ // Resolve when ID was resolved as otherwise owner will always be null
+ resolve(new CSteamDiscussion(this, discussion));
});
} catch (err) {
- callback(err, null);
+ reject(err);
}
- }, 'steamcommunity');
+ });
};
diff --git a/components/discussions.js b/components/discussions.js
index 43eeadb..0263805 100644
--- a/components/discussions.js
+++ b/components/discussions.js
@@ -1,4 +1,5 @@
const Cheerio = require('cheerio');
+const StdLib = require('@doctormckay/stdlib');
const SteamCommunity = require('../index.js');
const Helpers = require('./helpers.js');
@@ -12,16 +13,16 @@ const Helpers = require('./helpers.js');
* @param {function} callback - First argument is null/Error, second is array containing the requested comments
*/
SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIndex, callback) {
- this.httpRequestGet(url + '?l=en', async (err, res, body) => {
-
- if (err) {
- callback('Failed to load discussion: ' + err, null);
- return;
- }
+ return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
+ let result = await this.httpRequest({
+ method: 'GET',
+ url: url + '?l=en',
+ source: 'steamcommunity'
+ });
// Load output into cheerio to make parsing easier
- let $ = Cheerio.load(body);
+ let $ = Cheerio.load(result.textBody);
let paging = $('.forum_paging > .forum_paging_summary').children();
@@ -52,16 +53,20 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
if (i == 0) continue; // First page is already in pages object
promises.push(new Promise((resolve) => {
- setTimeout(() => { // Delay fetching a bit to reduce the risk of Steam blocking us
+ setTimeout(async () => { // Delay fetching a bit to reduce the risk of Steam blocking us
- this.httpRequestGet(url + '?l=en&ctp=' + (i + 1), (err, res, body) => {
- try {
- pages[i] = Cheerio.load(body);
- resolve();
- } catch (err) {
- return callback('Failed to load comments page: ' + err, null);
- }
- }, 'steamcommunity');
+ let commentsPage = await this.httpRequest({
+ method: 'GET',
+ url: url + '?l=en&ctp=' + (i + 1),
+ source: 'steamcommunity'
+ });
+
+ try {
+ pages[i] = Cheerio.load(commentsPage.textBody);
+ resolve();
+ } catch (err) {
+ return reject('Failed to load comments page: ' + err);
+ }
}, 250 * i);
}));
@@ -129,10 +134,10 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
}
- // Callback our result
- callback(null, comments);
+ // Resolve with our result
+ resolve(comments);
- }, 'steamcommunity');
+ });
};
/**
@@ -144,33 +149,27 @@ SteamCommunity.prototype.getDiscussionComments = function(url, startIndex, endIn
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum, discussionId, message, callback) {
- this.httpRequestPost({
- uri: `https://steamcommunity.com/comment/ForumTopic/post/${topicOwner}/${gidforum}/`,
- form: {
- comment: message,
- count: 15,
- sessionid: this.getSessionID(),
- extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- feature2: discussionId,
- json: 1
- },
- json: true
- }, function(err, response, body) {
- if (!callback) {
- return;
- }
-
- if (err) {
- callback(err);
- return;
- }
-
- if (body.success) {
- callback(null);
+ return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
+ let result = await this.httpRequest({
+ method: 'POST',
+ url: `https://steamcommunity.com/comment/ForumTopic/post/${topicOwner}/${gidforum}/`,
+ form: {
+ comment: message,
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
+ feature2: discussionId,
+ json: 1
+ },
+ source: 'steamcommunity'
+ });
+
+ if (result.jsonBody.success) {
+ resolve(null);
} else {
- callback(new Error(body.error));
+ reject(new Error(result.jsonBody.error));
}
- }, 'steamcommunity');
+ });
};
/**
@@ -182,33 +181,27 @@ SteamCommunity.prototype.postDiscussionComment = function(topicOwner, gidforum,
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum, discussionId, gidcomment, callback) {
- this.httpRequestPost({
- uri: `https://steamcommunity.com/comment/ForumTopic/delete/${topicOwner}/${gidforum}/`,
- form: {
- gidcomment: gidcomment,
- count: 15,
- sessionid: this.getSessionID(),
- extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- feature2: discussionId,
- json: 1
- },
- json: true
- }, function(err, response, body) { // Steam does not seem to return any errors here even when trying to delete a non-existing comment but let's check the response anyway
- if (!callback) {
- return;
- }
-
- if (err) {
- callback(err);
- return;
- }
-
- if (body.success) {
- callback(null);
+ return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
+ let result = await this.httpRequest({
+ method: 'POST',
+ url: `https://steamcommunity.com/comment/ForumTopic/delete/${topicOwner}/${gidforum}/`,
+ form: {
+ gidcomment: gidcomment,
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
+ feature2: discussionId,
+ json: 1
+ },
+ source: 'steamcommunity'
+ });
+
+ if (result.jsonBody.success) {
+ resolve(null);
} else {
- callback(new Error(body.error));
+ reject(new Error(result.jsonBody.error));
}
- }, 'steamcommunity');
+ });
};
/**
@@ -219,33 +212,27 @@ SteamCommunity.prototype.deleteDiscussionComment = function(topicOwner, gidforum
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidforum, discussionId, callback) {
- this.httpRequestPost({
- uri: `https://steamcommunity.com/comment/ForumTopic/subscribe/${topicOwner}/${gidforum}/`,
- form: {
- count: 15,
- sessionid: this.getSessionID(),
- extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
- feature2: discussionId,
- json: 1
- },
- json: true
- }, function(err, response, body) {
- if (!callback) {
- return;
- }
-
- if (err) {
- callback(err);
- return;
- }
-
- if (body.success && body.success != SteamCommunity.EResult.OK) {
- callback(Helpers.eresultError(body.success));
+ return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
+ let result = await this.httpRequest({
+ method: 'POST',
+ url: `https://steamcommunity.com/comment/ForumTopic/subscribe/${topicOwner}/${gidforum}/`,
+ form: {
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{"topic_permissions":{"can_view":1,"can_post":1,"can_reply":1}}',
+ feature2: discussionId,
+ json: 1
+ },
+ source: 'steamcommunity'
+ });
+
+ if (result.jsonBody.success && result.jsonBody.success != SteamCommunity.EResult.OK) {
+ reject(Helpers.eresultError(result.jsonBody.success));
return;
}
- callback(null);
- }, 'steamcommunity');
+ resolve(null);
+ });
};
/**
@@ -256,33 +243,27 @@ SteamCommunity.prototype.subscribeDiscussionComments = function(topicOwner, gidf
* @param {function} callback - Takes only an Error object/null as the first argument
*/
SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gidforum, discussionId, callback) {
- this.httpRequestPost({
- uri: `https://steamcommunity.com/comment/ForumTopic/unsubscribe/${topicOwner}/${gidforum}/`,
- form: {
- count: 15,
- sessionid: this.getSessionID(),
- extended_data: '{}', // Unsubscribing does not require any data here
- feature2: discussionId,
- json: 1
- },
- json: true
- }, function(err, response, body) {
- if (!callback) {
+ return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
+ let result = await this.httpRequest({
+ method: 'POST',
+ url: `https://steamcommunity.com/comment/ForumTopic/unsubscribe/${topicOwner}/${gidforum}/`,
+ form: {
+ count: 15,
+ sessionid: this.getSessionID(),
+ extended_data: '{}', // Unsubscribing does not require any data here
+ feature2: discussionId,
+ json: 1
+ },
+ source: 'steamcommunity'
+ });
+
+ if (result.jsonBody.success && result.jsonBody.success != SteamCommunity.EResult.OK) {
+ reject(Helpers.eresultError(result.jsonBody.success));
return;
}
- if (err) {
- callback(err);
- return;
- }
-
- if (body.success && body.success != SteamCommunity.EResult.OK) {
- callback(Helpers.eresultError(body.success));
- return;
- }
-
- callback(null);
- }, 'steamcommunity');
+ resolve(null);
+ });
};
/**
@@ -293,29 +274,23 @@ SteamCommunity.prototype.unsubscribeDiscussionComments = function(topicOwner, gi
SteamCommunity.prototype.setDiscussionCommentsPerPage = function(value, callback) {
if (!['15', '30', '50'].includes(value)) value = '50'; // Check for invalid setting
- this.httpRequestPost({
- uri: 'https://steamcommunity.com/forum/0/0/setpreference',
- form: {
- preference: 'topicrepliesperpage',
- value: value,
- sessionid: this.getSessionID(),
- },
- json: true
- }, function(err, response, body) { // Steam does not seem to return any errors for this request
- if (!callback) {
- return;
- }
-
- if (err) {
- callback(err);
- return;
- }
-
- if (body.success && body.success != SteamCommunity.EResult.OK) {
- callback(Helpers.eresultError(body.success));
+ return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
+ let result = await this.httpRequest({
+ method: 'POST',
+ url: 'https://steamcommunity.com/forum/0/0/setpreference',
+ form: {
+ preference: 'topicrepliesperpage',
+ value: value,
+ sessionid: this.getSessionID(),
+ },
+ source: 'steamcommunity'
+ });
+
+ if (result.jsonBody.success && result.jsonBody.success != SteamCommunity.EResult.OK) {
+ reject(Helpers.eresultError(result.jsonBody.success));
return;
}
- callback(null);
- }, 'steamcommunity');
+ resolve(null);
+ });
};
\ No newline at end of file
From 3c7972ae173736275ac2630f590d6112fdecabb1 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sat, 2 Mar 2024 12:33:39 +0100
Subject: [PATCH 20/21] feat(Discussions): Add eventcomments support
---
classes/CSteamDiscussion.js | 45 +++++++++++++++++++++---------------
resources/EDiscussionType.js | 6 +++--
2 files changed, 30 insertions(+), 21 deletions(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index 18082ac..bf0775c 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -63,6 +63,7 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
if (url.includes('steamcommunity.com/discussions/forum')) discussion.type = EDiscussionType.Forum;
if (/steamcommunity.com\/app\/.+\/discussions/g.test(url)) discussion.type = EDiscussionType.App;
if (/steamcommunity.com\/groups\/.+\/discussions/g.test(url)) discussion.type = EDiscussionType.Group;
+ if (/steamcommunity.com\/app\/.+\/eventcomments/g.test(url)) discussion.type = EDiscussionType.Eventcomments;
// Get appID from breadcrumbs if this discussion is associated to one
@@ -73,16 +74,18 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
}
- // Get forumID from breadcrumbs
- let forumIdHref;
+ // Get forumID from breadcrumbs - Ignore for type Eventcomments as it doesn't have multiple forums
+ if (discussion.type != EDiscussionType.Eventcomments) {
+ let forumIdHref;
- if (discussion.type == EDiscussionType.Group) { // Groups have an extra breadcrumb so we need to shift by 2
- forumIdHref = breadcrumbs[4].attribs.href.split('/');
- } else {
- forumIdHref = breadcrumbs[2].attribs.href.split('/');
- }
+ if (discussion.type == EDiscussionType.Group) { // Groups have an extra breadcrumb so we need to shift by 2
+ forumIdHref = breadcrumbs[4].attribs.href.split('/');
+ } else {
+ forumIdHref = breadcrumbs[2].attribs.href.split('/');
+ }
- discussion.forumID = forumIdHref[forumIdHref.length - 2];
+ discussion.forumID = forumIdHref[forumIdHref.length - 2];
+ }
// Get id, gidforum and topicOwner. The first is used in the URL itself, the other two only in post requests
@@ -119,20 +122,24 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
}
- // Find author and convert to SteamID object
- let authorLink = $('.authorline > .forum_op_author').attr('href');
+ // Find author and convert to SteamID object - Ignore for type Eventcomments as they are posted by the "game", not by an Individual
+ if (discussion.type != EDiscussionType.Eventcomments) {
+ let authorLink = $('.authorline > .forum_op_author').attr('href');
- Helpers.resolveVanityURL(authorLink, (err, data) => { // This request takes <1 sec
- if (err) {
- reject(err);
- return;
- }
+ Helpers.resolveVanityURL(authorLink, (err, data) => { // This request takes <1 sec
+ if (err) {
+ reject(err);
+ return;
+ }
- discussion.author = new SteamID(data.steamID);
+ discussion.author = new SteamID(data.steamID);
- // Resolve when ID was resolved as otherwise owner will always be null
+ // Resolve when ID was resolved as otherwise owner will always be null
+ resolve(new CSteamDiscussion(this, discussion));
+ });
+ } else {
resolve(new CSteamDiscussion(this, discussion));
- });
+ }
} catch (err) {
reject(err);
@@ -145,7 +152,7 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
* Constructor - Creates a new Discussion object
* @class
* @param {SteamCommunity} community
- * @param {{ id: string, appID: string, forumID: string, author: SteamID, postedDate: Object, title: string, content: string, commentsAmount: number }} data
+ * @param {{ id: string, type: EDiscussionType, appID: string, forumID: string, gidforum: string, topicOwner: string, author: SteamID, postedDate: Object, title: string, content: string, commentsAmount: number, answerCommentIndex: number }} data
*/
function CSteamDiscussion(community, data) {
/**
diff --git a/resources/EDiscussionType.js b/resources/EDiscussionType.js
index 99bcd60..30996bd 100644
--- a/resources/EDiscussionType.js
+++ b/resources/EDiscussionType.js
@@ -5,9 +5,11 @@ module.exports = {
Forum: 0,
App: 1,
Group: 2,
+ Eventcomments: 3,
// Value-to-name mapping for convenience
0: 'Forum',
1: 'App',
- 2: 'Group'
-};
\ No newline at end of file
+ 2: 'Group',
+ 3: 'Eventcomments'
+};
From 985c2a54d743b0ae3c9c33e63dd3c2a1a60b1466 Mon Sep 17 00:00:00 2001
From: 3urobeat <35304405+3urobeat@users.noreply.github.com>
Date: Sat, 2 Mar 2024 13:18:30 +0100
Subject: [PATCH 21/21] feat(Discussions): Add accountCanComment prop
---
classes/CSteamDiscussion.js | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/classes/CSteamDiscussion.js b/classes/CSteamDiscussion.js
index bf0775c..b6706b2 100644
--- a/classes/CSteamDiscussion.js
+++ b/classes/CSteamDiscussion.js
@@ -27,14 +27,15 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
title: null,
content: null,
commentsAmount: null, // I originally wanted to fetch all comments by default but that would have been a lot of potentially unused data
- answerCommentIndex: null
+ answerCommentIndex: null,
+ accountCanComment: null // Is this account allowed to comment on this discussion?
};
// Get DOM of discussion
return StdLib.Promises.callbackPromise(null, callback, true, async (resolve, reject) => {
let result = await this.httpRequest({
method: 'GET',
- url: url,
+ url: url + '?l=en',
source: 'steamcommunity'
});
@@ -122,6 +123,12 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
}
+ // Check if this account is allowed to comment on this discussion
+ let cannotReplyReason = $('.topic_cannotreply_reason');
+
+ discussion.accountCanComment = cannotReplyReason.length == 0;
+
+
// Find author and convert to SteamID object - Ignore for type Eventcomments as they are posted by the "game", not by an Individual
if (discussion.type != EDiscussionType.Eventcomments) {
let authorLink = $('.authorline > .forum_op_author').attr('href');
@@ -152,7 +159,7 @@ SteamCommunity.prototype.getSteamDiscussion = function(url, callback) {
* Constructor - Creates a new Discussion object
* @class
* @param {SteamCommunity} community
- * @param {{ id: string, type: EDiscussionType, appID: string, forumID: string, gidforum: string, topicOwner: string, author: SteamID, postedDate: Object, title: string, content: string, commentsAmount: number, answerCommentIndex: number }} data
+ * @param {{ id: string, type: EDiscussionType, appID: string, forumID: string, gidforum: string, topicOwner: string, author: SteamID, postedDate: Object, title: string, content: string, commentsAmount: number, answerCommentIndex: number, accountCanComment: boolean }} data
*/
function CSteamDiscussion(community, data) {
/**