This originated here:
https://bitcointalksearch.org/topic/m.49470283And I completely support the idea raised by that thread, i.e. make it easier to report certain types of posts. But until/unless that feature gets implemented perhaps you'll find my approach useful.
Caveats1. This is a browser extension. Never install browser extensions obtained from random strangers on the internet unless you know exactly what you're doing. I'm not responsible if this extension steals all your money and runs away with your spouse.
2. This is mostly-untested code. It's alpha quality at best. I gutted it out of a larger extension that I'm using for other purposes. Treat it as an example or an idea. Feel free to steal any bits and pieces that you like or do whatever you want with it.
3. It works in Firefox/Tor browser and may work in Chrome but again - untested. Probably won't work in mobile browsers.
4. You must have some knowledge of how WebExtensions and Promises work in order to make sense of the code. I'll try to explain below but a proper tutorial is out of the scope of this thread.
How this worksThis is a browser extension built using WebExtensions API and consists of two main parts:
- bct-content.js: content script, a copy of which runs on every Bitcointalk page once the extension is installed. Depending on which page it is, the content script will perform different actions:
- Inside threads, on Patrol page, and in user's post/thread history it creates one-click buttons to report posts to moderators. Clicking one of the buttons sends a message to the other part of the extension, which handles queuing etc.
Edited 2020-11-30 to fix a broken image - On the "Report to moderator" page it expands the comment field for better visibility. If the page was opened automatically by one of the above buttons, it inserts the pre-selected comment, waits a specified amount of time to comply with throttling, and clicks "Submit".
- In the board view it closes the tab if it came from the automated post report, otherwise it does nothing.
- bct-background.js: background script, which listens to messages from content scripts and for each message opens the "Report to moderator" in a new tab, which is then handled by the content script as described above. This is done without taking focus away from what you're doing so you can keep browsing and reporting other posts.
The code that runs between when you click your button and when the extension clicks the "Submit" button is wrapped in a
Promise so it should succeed or fail as a whole. Please note though that the actual submission of the report is not verified, i.e. if there is a failure
after "Submit" was clicked - Bitcointalk down, CF fire hydrant, etc - you'll have to double-check your report history to see if the report was submitted successfully.
If the code succeeds it will add a yellow border on the left side of the post being reported, if it fails - a red one. Styling is configurable in bct-content.css. o_e_l_e_o created a
custom stylesheet for a "blend in" look-and-feel.
In case of a failure check console log and please let me know if there is a bug that's causing it.
How to installSave the files posted below into one folder. In Firefox/Tor go to "about:debugging", click Load Temporary Add-on, and choose any file in the folder. If you're brave enough to use Chrome -
you're gonna have to figure it out on your own HCP has
figured out a way.
The actual clicking of the "Submit" button is commented out - see line 18 in bct-content.js. I would like to avoid spamming our dear moderators with a bunch of malformed reports if anything goes wrong. If you decide to use the extension you can test it first by manually clicking "Submit" and then uncomment the line once you're comfortable.
Line 19 sets the wait time before clicking "Submit". High activity accounts have a 4-seconds-betwen-posts allowance, so the default 5000ms delay works well with that. With a lower activity account you may run into throttling - adjust the delay as needed.
You can change button titles, report comments, add new buttons, etc around line 120 in bct-content.js.
Questions?I'm sure I missed something or messed something up. Feel free to comment in the thread.
Filesmanifest.json
{
"manifest_version": 2,
"name": "BCT Helper",
"version": "0.1b",
"description": "Adds some automation for bitcointalk.org.",
"content_scripts": [
{
"matches": [ "*://bitcointalk.org/*" ],
"js": [ "bct-content.js" ],
"css": [ "bct-content.css" ],
"run_at": "document_idle"
}
],
"background":
{
"scripts": ["bct-background.js"]
},
"permissions": [
"tabs"
]
}
bct-content.css
div.post {
border-left: 4px transparent solid;
}
div.post.post-wait {
opacity: 0.5;
}
div.post.post-error {
border-left: 4px red solid;
}
div.post.post-success {
border-left: 4px yellow solid;
}
.bct-report-button-container {
margin-top: 10px;
background-color: #bbddbb;
}
.bct-report-input {
margin-left: 5px;
height: 12px;
}
.bct-report-button, .bct-report-button:hover {
display: inline-block;
border: 1px solid black;
margin-left: 5px;
padding: 1px 5px 1px 5px;
transform: none;
}
.bct-report-button:hover {
cursor: pointer;
}
bct-content.js
console.log("BCT-CONTENT initialized");
console.log("Page: " + window.location.href);
console.log("Referrer: " + document.referrer);
function process_background_message(message, sender, send_response) {
browser.runtime.onMessage.removeListener(process_background_message);
console.log("Content script received background message: " + JSON.stringify(message));
if (message.action == "bct-tab-open-report" || message.action == "bct-tab-submit-report") {
if (message.comment !== undefined) {
document.getElementsByName("comment")[0].value = message.comment;
}
document.getElementsByName("comment")[0].focus();
message.result = "OK";
}
if (message.action == "bct-tab-submit-report") {
// mod report counts as post/PM for throttling - add a delay
setTimeout(() => {
send_response(message);
// Uncomment the next line to allow reports to be submitted automatically
//document.querySelector("input[type=submit][value=Submit]").click();
}, 5000);
} else {
send_response(message);
}
// this is needed to make the sender wait for a response
return true;
}
function report_post(post_container, thread_id, post_id, report_comment, auto_submit) {
post_container.classList.add("post-wait");
let event_detail = {
event_id: (Math.random().toString(36) + '000000000000000000').slice(2, 18),
action_name: "bct-report",
action_url: "https://bitcointalk.org/index.php?action=reporttm;topic=" + thread_id + ";msg=" + post_id,
action_payload: { post_id: post_id, comment: report_comment, auto: auto_submit }
};
browser.runtime.sendMessage(event_detail)
.then((message_response) => {
//console.log("message_response: " + JSON.stringify(message_response));
console.log("message_response size: " + JSON.stringify(message_response).length);
post_container.classList.remove("post-wait", "post-error", "post-success");
post_container.classList.add("post-success");
})
.catch((error) => {
console.log("Data request failed:");
console.log(error);
post_container.classList.remove("post-wait", "post-error", "post-success");
post_container.classList.add("post-error");
})
;
}
function extract_ids_from_url(post_url) {
let url_parts = post_url.split("#msg");
let post_id = url_parts[1];
let thread_id = url_parts[0].split(".msg")[0].split("?topic=")[1];
return [thread_id, post_id];
}
function create_button(post_container, button_title, report_comment, text_field, auto_submit) {
let button = document.createElement("button");
button.className = "bct-report-button";
button.innerText = button_title;
button.title = report_comment;
button.addEventListener("click", (e) => {
e.preventDefault();
if (text_field) {
if (text_field.value.trim()) {
report_comment += " " + text_field.value.trim();
} else {
alert("Required value missing");
return;
}
}
report_post(post_container, post_container.thread_id, post_container.post_id, report_comment, auto_submit);
});
return button;
}
function create_span(text) {
let span = document.createElement("span");
span.innerText = text;
return span;
}
function create_text_field(hint) {
let text_field = document.createElement("input");
text_field.className = "bct-report-input";
text_field.type = "text";
text_field.placeholder = hint;
return text_field;
}
// inject the buttons into each message
document.querySelectorAll("div.post").forEach(post_container => {
// Try to determine thread ID and post ID
let link_object = null;
if (post_container.parentNode.classList.contains("td_headerandpost")) {
// Thread view
// post -> td.td_headerandpost -> table ... -> div#subject_123456
link_object = post_container.parentNode.firstElementChild.querySelector("div[id^='subject_'] a");
} else {
// Other views: patrol, user's post history, user's thread history
let post_url_start = "https://bitcointalk.org/index.php?topic=";
// post -> td -> tr -> tbody -> tr ... -> a[href contains #msg123456]
link_object = post_container.parentNode.parentNode.parentNode.firstElementChild.querySelector("a[href^='" + post_url_start + "'][href*='#msg']");
}
if (link_object) {
[post_container.thread_id, post_container.post_id] = extract_ids_from_url(link_object.getAttribute("href"));
if (post_container.thread_id && post_container.post_id) {
let button_container = document.createElement("div");
button_container.className = "bct-report-button-container";
post_container.appendChild(button_container);
button_container.appendChild(create_span("Report as: "));
button_container.appendChild(create_button(post_container, "zero value", "zero-value shitpost", null, true));
button_container.appendChild(create_button(post_container, "multi post", "two or more consecutive posts in 24h", null, true));
button_container.appendChild(create_button(post_container, "cross spam", "spamming their service across multiple threads - please check post history", null, true));
button_container.appendChild(create_button(post_container, "non-english", "non-English post on English board", null, true));
let url_field = create_text_field("URL of the original");
button_container.appendChild(create_button(post_container, "copy from:", "copy-paste from:", url_field, true));
button_container.appendChild(url_field);
let board_field = create_text_field("correct board name");
button_container.appendChild(create_button(post_container, "move to:", "wrong board, should be in", board_field, true));
button_container.appendChild(board_field);
} else {
console.log("Found div.post and post URL but couldn't determine thread/post ID.");
}
} else {
console.log("Found div.post but couldn't find post URL.");
}
});
if (window.location.href.startsWith("https://bitcointalk.org/index.php?action=reporttm")) {
document.getElementsByName("comment")[0].style.width = "80%";
browser.runtime.onMessage.addListener(process_background_message);
}
if (window.location.href.startsWith("https://bitcointalk.org/index.php?board=")) {
if (document.referrer &&
document.referrer.startsWith("https://bitcointalk.org/index.php?action=reporttm") &&
document.referrer.endsWith(";a") // after automatic submission
) {
console.log("Attempting to close this tab...");
browser.runtime.sendMessage({ action_name: "close-this-tab" });
}
}
bct-background.js
// This is an array of Promise.resolve functions that will be called sequentially with delay
let throttled_resolvers = [];
// Number of milliseconds to wait before resolving the next queued promise
let PROMISE_INTERVAL = 1300;
// Number of milliseconds to wait before rejecting the queued promise
let PROMISE_TIMEOUT = 120000;
// Number of milliseconds to wait for a tab to load
let TAB_TIMEOUT = 60000;
function handle_next_resolver() {
let p = throttled_resolvers.shift();
if (p === undefined) {
setTimeout(handle_next_resolver, PROMISE_INTERVAL);
}
else {
p.resolve();
}
}
setTimeout(handle_next_resolver, PROMISE_INTERVAL);
function queue_promise() {
return new Promise((resolve, reject) => {
throttled_resolvers.push({ resolve: resolve });
setTimeout(function () { reject(new Error("Queued promise has timed out.")); }, PROMISE_TIMEOUT);
});
}
function check_if_tab_fully_loaded(tab) {
function is_tab_complete(tab) {
return tab.status === "complete" && tab.url !== "about:blank";
}
if (is_tab_complete(tab)) {
return tab;
} else {
return new Promise((resolve, reject) => {
const timer = setTimeout(
function () {
browser.tabs.onUpdated.removeListener(on_updated);
if (is_tab_complete(tab)) {
resolve(tab);
} else {
reject(new Error("Tab status " + tab.status + ": " + tab.url));
}
},
TAB_TIMEOUT
);
function on_updated(tab_id, change_info, updated_tab) {
if (tab_id == tab.id && is_tab_complete(updated_tab)) {
clearTimeout(timer);
browser.tabs.onUpdated.removeListener(on_updated);
resolve(updated_tab);
}
}
browser.tabs.onUpdated.addListener(on_updated);
});
}
}
browser.runtime.onMessage.addListener(function(message, sender) {
if (message.action_name === "close-this-tab") {
//console.log("Background script closing tab:");
//console.log(sender.tab);
browser.tabs.remove(sender.tab.id);
}
else if (message.action_name === "bct-report") {
/*
Expected message format:
{
action_name: "bct-auto-report",
action_url: "https://...",
action_payload: { post_id: N, comment: "...", auto: true }
}
*/
let tab_url = message.action_url;
let tab_action = "bct-tab-open-report";
if (message.action_payload.auto) {
tab_action = "bct-tab-submit-report";
tab_url += ";a";
}
console.log(message);
return queue_promise()
.then(() =>
browser.tabs.create({
url: tab_url,
windowId: sender.tab.windowId,
active: false
})
)
.then((created_tab) => check_if_tab_fully_loaded(created_tab))
.catch((error) => {
error_message = "Tab load/check failed: " + error.message;
console.log(error_message);
throw new Error(error_message);
})
.then((loaded_tab) => browser.tabs.sendMessage(loaded_tab.id, { id: loaded_tab.id, action: tab_action, comment: message.action_payload.comment }))
.then((tab_response) => {
//console.log("Tab result: " + tab_response.result);
message.action_result = tab_response.result;
return message;
})
.catch((error) => {
console.log("Request failed in the background:");
console.log(error);
throw new Error(error.message);
})
.finally(() => {
setTimeout(handle_next_resolver, PROMISE_INTERVAL);
})
;
}
});
Local rulescryptohunter and Thule are not allowed to post here. This thread is only about mod reporting and improvement thereof, not DT, merits, or anything else.