Skip to content

Instantly share code, notes, and snippets.

@Luckz
Last active July 27, 2026 18:49
Show Gist options
  • Select an option

  • Save Luckz/3ee02911d57da5b1793eb221beb6f169 to your computer and use it in GitHub Desktop.

Select an option

Save Luckz/3ee02911d57da5b1793eb221beb6f169 to your computer and use it in GitHub Desktop.
SteamCommentAnalysis.user.js
const assert = require('node:assert/strict');
const {
findRawComment,
formatBBCode,
placeNotice,
placeholdersInNode,
} = require('./SteamCommentAnalysis.user.js');
assert.equal(
formatBBCode('[b]bold[/b]\n[url=javascript:alert(1)]unsafe[/url]\n<script>x</script>'),
'<b>bold</b><br><a class="bb_link">unsafe</a><br>&lt;script&gt;x&lt;/script&gt;'
);
assert.equal(
formatBBCode('[quote=Alice;123]Hello [i]there[/i][/quote]'),
'<blockquote class="bb_blockquote with_author"><div class="bb_quoteauthor">Originally posted by <b><a href="#c123">Alice</a></b>:</div>Hello <i>there</i></blockquote>'
);
const expected = { text: 'found' };
const threads = {
wrong: { GetRawComment: () => null },
right: { GetRawComment: id => id === '42' ? expected : null },
};
assert.equal(findRawComment(threads, '42'), expected);
const placeholder = { nodeType: 1, matches: selector => selector === '.needs_content_check' };
assert.deepEqual(placeholdersInNode(placeholder), [placeholder]);
const container = {
nodeType: 1,
matches: () => false,
querySelectorAll: selector => selector === '.needs_content_check' ? [placeholder] : [],
};
assert.deepEqual(placeholdersInNode(container), [placeholder]);
const timestamp = {};
let insertion;
const authorNameGroup = {
querySelector: selector => selector === '.commentthread_comment_timestamp' ? timestamp : null,
insertBefore: (notice, reference) => { insertion = { notice, reference }; },
};
const comment = {
querySelector: selector => selector === '.author_name_group' ? authorNameGroup : null,
};
const notice = {};
assert.equal(placeNotice(comment, notice), true);
assert.deepEqual(insertion, { notice, reference: timestamp });
console.log('SteamCommentAnalysis.user.js tests passed');
// ==UserScript==
// @name [AI] Steam Community: Show comments awaiting analysis
// @namespace luckz
// @author luckz
// @LLMs GPT-5.6-SOL
// @version 0.8.5
// @description Shows forum comments stuck in Valve's automated content check system
// @match https://steamcommunity.com/groups/*/discussions/*
// @match https://steamcommunity.com/app/*/discussions/*
// @match https://steamcommunity.com/app/*/eventcomments/*
// @match https://steamcommunity.com/discussions/forum/*
// @grant none
// @run-at document-body
// @og-author Ryzhehvost
// @source https://greasyfork.org/en/scripts/409913-show-not-checked-content
// @license Apache-2.0
// @downloadURL https://gist.github.com/Luckz/3ee02911d57da5b1793eb221beb6f169/raw/SteamCommentAnalysis.user.js
// ==/UserScript==
'use strict';
function escapeHTML(value) {
return String(value)
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
}
function safeURL(value) {
try {
const url = new URL(value, 'https://steamcommunity.com/');
return ['http:', 'https:', 'steam:'].includes(url.protocol) ? value : null;
} catch (_) {
return null;
}
}
function openingTag(name, argument) {
const simpleTags = {
b: ['<b>', '</b>'],
i: ['<i>', '</i>'],
u: ['<u>', '</u>'],
h1: ['<div class="bb_h1">', '</div>'],
strike: ['<span class="bb_strike">', '</span>'],
spoiler: ['<span class="bb_spoiler"><span>', '</span></span>'],
list: ['<ul class="bb_ul">', '</ul>'],
olist: ['<ol>', '</ol>'],
};
if (simpleTags[name]) {
return simpleTags[name];
}
if (name === 'url' && argument !== null) {
const url = safeURL(argument);
return url
? [`<a class="bb_link" href="${escapeHTML(url)}" target="_blank" rel="noreferrer">`, '</a>']
: ['<a class="bb_link">', '</a>'];
}
if (name === 'quote') {
if (argument === null) {
return ['<blockquote class="bb_blockquote">', '</blockquote>'];
}
const separator = argument.lastIndexOf(';');
const author = separator === -1 ? argument : argument.slice(0, separator);
const commentID = separator === -1 ? '' : argument.slice(separator + 1);
const escapedAuthor = escapeHTML(author);
const attribution = /^\d+$/.test(commentID)
? `<a href="#c${commentID}">${escapedAuthor}</a>`
: escapedAuthor;
return [
`<blockquote class="bb_blockquote with_author"><div class="bb_quoteauthor">Originally posted by <b>${attribution}</b>:</div>`,
'</blockquote>',
];
}
return null;
}
function formatBBCode(text) {
const tokenPattern = /\[\/?([a-z]+)(?:=([^\]]*))?\]/gi;
const stack = [];
let output = '';
let position = 0;
let match;
const appendText = value => {
output += escapeHTML(value).replace(/\r?\n/g, '<br>');
};
while ((match = tokenPattern.exec(text)) !== null) {
appendText(text.slice(position, match.index));
position = tokenPattern.lastIndex;
const closing = match[0][1] === '/';
const name = match[1].toLowerCase();
const argument = match[2] === undefined ? null : match[2];
if (!closing && (name === 'noparse' || name === 'code')) {
const endPattern = new RegExp(`\\[/${name}\\]`, 'ig');
endPattern.lastIndex = position;
const end = endPattern.exec(text);
if (!end) {
appendText(match[0]);
continue;
}
const content = escapeHTML(text.slice(position, end.index));
output += name === 'code' ? `<div class="bb_code">${content}</div>` : content;
position = endPattern.lastIndex;
tokenPattern.lastIndex = position;
continue;
}
if (closing) {
const open = stack.at(-1);
if (open && open.name === name) {
output += open.close;
stack.pop();
} else {
appendText(match[0]);
}
continue;
}
const tag = openingTag(name, argument);
if (!tag) {
appendText(match[0]);
continue;
}
output += tag[0];
stack.push({ name, close: tag[1] });
}
appendText(text.slice(position));
while (stack.length) {
output += stack.pop().close;
}
return output;
}
function findRawComment(threadCollections, commentID) {
const collections = Array.isArray(threadCollections) ? threadCollections : [threadCollections];
for (const threads of collections) {
if (!threads) {
continue;
}
for (const key of Object.keys(threads)) {
try {
const rawComment = threads[key]?.GetRawComment?.(commentID);
if (rawComment?.text !== undefined) {
return rawComment;
}
} catch (_) {
// This comment belongs to a different thread.
}
}
}
return null;
}
function placeholdersInNode(node) {
if (node.nodeType !== 1) {
return [];
}
const placeholders = node.matches('.needs_content_check') ? [node] : [];
return placeholders.concat(Array.from(node.querySelectorAll?.('.needs_content_check') || []));
}
function placeNotice(comment, notice) {
const authorNameGroup = comment.querySelector('.author_name_group');
const timestamp = authorNameGroup?.querySelector('.commentthread_comment_timestamp');
if (!timestamp) {
return false;
}
authorNameGroup.insertBefore(notice, timestamp);
return true;
}
function start() {
const threadCollections = [
globalThis.g_rgForumTopicCommentThreads,
globalThis.g_rgCommentThreads,
];
function reveal(placeholder) {
const comment = placeholder.closest('.commentthread_comment');
const match = comment?.id.match(/^comment_(\d+)$/);
if (!match) {
return;
}
const rawComment = findRawComment(threadCollections, match[1]);
if (!rawComment) {
return;
}
const content = placeholder.closest('.commentthread_comment_text');
if (!content) {
return;
}
content.innerHTML = formatBBCode(rawComment.text);
if (!comment.querySelector('.improved_content_check_notice')) {
const notice = document.createElement('span');
notice.className = 'forum_comment_author_banned improved_content_check_notice';
notice.textContent = '(comment is awaiting analysis)';
placeNotice(comment, notice);
}
}
document.querySelectorAll('.needs_content_check').forEach(reveal);
const observer = new MutationObserver(mutations => {
for (const mutation of mutations) {
for (const node of mutation.addedNodes) {
placeholdersInNode(node).forEach(reveal);
}
}
});
document.querySelectorAll('.commentthread_area').forEach(area => {
observer.observe(area, { childList: true, subtree: true });
});
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = { findRawComment, formatBBCode, placeNotice, placeholdersInNode };
} else {
start();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment