'Saviour' of Lost Souls v1.0: Stacks design & option to post comment anyway

This commit is contained in:
Glorfindel 2019-10-09 08:49:33 +02:00
parent cc558ea804
commit ab5ef30ae4

View file

@ -5,7 +5,7 @@
// @author Glorfindel
// @updateURL https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/saviour-of-lost-souls/saviour-of-lost-souls.user.js
// @downloadURL https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/saviour-of-lost-souls/saviour-of-lost-souls.user.js
// @version 0.5.2
// @version 1.0
// @match *://meta.stackexchange.com/questions/*
// @match *://meta.stackoverflow.com/questions/*
// @match *://softwarerecs.stackexchange.com/questions/*
@ -38,7 +38,6 @@ waitForKeyElements('div.review-content div.question', function(jNode) {
});
function main(question) {
console.log(question)
// Check if author is likely to be a lost soul
let owner = question.find('div.post-signature.owner');
if (owner.length == 0)
@ -56,13 +55,52 @@ function main(question) {
if (numberOfReasons < 2)
return;
}
// Which site?
let isMetaSE = location.host == 'meta.stackexchange.com';
let isBeta = location.host == 'hardwarerecs.stackexchange.com';
// My reputation; you need 5 reputation to comment
// My reputation
let myReputation = parseInt($('a.my-profile div.-rep')[0].innerText.replace(/,/g, ''));
if (myReputation < 5) {
return;
}
let hasCommentPrivilege = myReputation >= (isMetaSE ? 5 : 50);
let hasFlagPrivilege = myReputation >= 15;
let hasUpvotePrivilege = myReputation >= 15;
let hasDownvotePrivilege = myReputation >= (isMetaSE ? 100 : 125);
let hasCloseVotePrivilege = myReputation >= (isBeta ? 500 : 3000);
let hasDeleteVotePrivilege = myReputation >= (isBeta ? 4000 : 20000);
let isModerator = $("a.js-mod-inbox-button").length > 0;
// Can the script do anything?
if (!hasCommentPrivilege && !hasFlagPrivilege)
return;
// Score; downvoted or not?
let downvoted = question.find('a.vote-down-on').length > 0;
let score = parseInt(question.find('div.js-vote-count')[0].innerText.replace(/,/g, ''));
// Closed?
let status = $('div.question-status h2 b');
let statusText = status.length > 0 ? status[0].innerText : '';
let closed = statusText == 'marked' || statusText == 'put on hold' || statusText == 'closed';
// Is there any comment not by the author?
let comments = question.find('ul.comments-list');
var hasNonOwnerComment = false;
comments.find('a.comment-user').each(function() {
if (!$(this).hasClass('owner')) {
hasNonOwnerComment = true;
}
});
// Determine which actions to take
// Comment
let shouldComment = hasCommentPrivilege && !hasNonOwnerComment;
// Downvote (not when the post is already on -3 or lower, to be slightly more welcoming)
let shouldDownvote = hasDownvotePrivilege && !downvoted && score > -3;
// Flag/vote to close
let shouldFlag = hasFlagPrivilege && !hasCloseVotePrivilege && !closed;
let shouldVoteToClose = hasCloseVotePrivilege && !closed;
// Vote to delete
let shouldVoteToDelete = (hasDeleteVotePrivilege && closed && score <= -3) || isModerator;
// Add post menu button
let menu = question.find('div.post-menu');
@ -70,34 +108,86 @@ function main(question) {
let button = $('<a href="#" title="down-/close-/delete vote and post a welcoming comment">lost soul</a>');
menu.append(button);
button.click(function() {
if (!confirm('Are you sure you want to down-/close-/delete vote and post a welcoming comment?'))
return;
// Downvoted?
let downvoted = question.find('a.vote-down-on').length > 0;
// Closed?
let status = $('div.question-status h2 b');
let statusText = status.length > 0 ? status[0].innerText : '';
let closed = statusText == 'marked' || statusText == 'put on hold' || statusText == 'closed';
// Generate HTML for dialog
var html = `
<aside class="s-modal bg-transparent pe-none js-stacks-managed-popup js-fades-with-aria-hidden" id="modal-base" tabindex="-1" role="dialog" aria-labelledby="mod-modal-title" aria-describedby="mod-modal-description" aria-hidden="false">
<form class="s-modal--dialog js-modal-dialog js-keyboard-navigable-modal pe-auto" role="document" data-controller="se-draggable se-mod-menu" data-se-mod-menu-model-type="post" data-se-mod-menu-model-id="371841" method="get" action="#" data-action="se-mod-menu#submit" data-keyboard-actions="#mod-screen-select-menu, input[name=action], .js-info-link">
<h1 class="s-modal--header fs-headline1 fw-bold mr48 c-move js-first-tabbable" id="modal-title" tabindex="0" data-target="se-draggable.handle">
'Saviour' of Lost Souls
</h1>`;
html += `
<div class="grid--cell">
<span class="js-short-label">Please confirm you want to</span>
<ul>`;
// List actions that will be taken
if (shouldComment) {
html += `
<li>leave a welcoming comment</li>`;
}
if (shouldDownvote) {
html += `
<li>downvote the question</li>`;
}
if (shouldFlag) {
html += `
<li>flag the question as off-topic</li>`;
}
if (shouldVoteToClose) {
html += `
<li>vote to close the question as off-topic</li>`;
}
if (shouldVoteToDelete) {
html += `
<li>vote to delete the question</li>`;
}
html += `
</ul>
</div>
<br/>`;
if (hasCommentPrivilege && hasNonOwnerComment) {
// Add option to post a comment anyway
html += `
<div class="grid--cell">
<span>Another user already posted a comment.</span>
<fieldset class="mt4 ml4 mb16">
<div class="grid gs8 gsx ff-row-nowrap">
<div class="grid--cell">
<input class="s-checkbox" type="checkbox" name="postCommentAnyway" id="sols-postCommentAnyway">
</div>
<label class="grid--cell s-label fw-normal" for="sols-postCommentAnyway">
Post a welcoming comment anyway
</label>
</div>
</fieldset>
</div>`;
}
html += `
<div class="grid gs8 gsx s-modal--footer">
<button class="grid--cell s-btn s-btn__primary" type="submit" onclick="saviourOfLostSouls.submitDialog();">Confirm</button>
<button class="grid--cell s-btn js-modal-close" type="button" onclick="saviourOfLostSouls.closeDialog();">Cancel</button>
</div>
<button class="s-modal--close s-btn s-btn__muted js-modal-close js-last-tabbable" type="button" aria-label="Close" onclick="saviourOfLostSouls.closeDialog();">
<svg aria-hidden="true" class="svg-icon m0 iconClearSm" width="14" height="14" viewBox="0 0 14 14"><path d="M12 3.41L10.59 2 7 5.59 3.41 2 2 3.41 5.59 7 2 10.59 3.41 12 7 8.41 10.59 12 12 10.59 8.41 7 12 3.41z"></path></svg>
</button>
</form>
</aside>`;
$(document.body).append($(html));
});
// Define functions which can be called by the dialog
window.saviourOfLostSouls = {};
saviourOfLostSouls.closeDialog = function() {
$("#modal-base").remove();
};
saviourOfLostSouls.submitDialog = function() {
// Prepare votes/comments
let postID = parseInt(question.attr('data-questionid'));
console.log('Lost soul #' + postID);
let fkey = window.localStorage["se:fkey"].split(",")[0];
// Is there any comment not by the author?
let comments = question.find('ul.comments-list');
var nonOwnerComment = false;
comments.find('a.comment-user').each(function() {
if (!$(this).hasClass('owner')) {
nonOwnerComment = true;
}
});
if (!nonOwnerComment) {
if (shouldComment || $("#sols-postCommentAnyway").prop("checked")) {
// Post comment
let author = owner.find('div.user-details a')[0].innerText;
let comment = window.location.host === "softwarerecs.stackexchange.com"
? ("Hi " + author + ", welcome to [softwarerecs.se]! " +
"This question does not appear to be about software recommendations, within [the scope defined on meta](https://softwarerecs.meta.stackexchange.com/questions/tagged/scope) and in the [help center](/help/on-topic). " +
@ -124,28 +214,26 @@ function main(question) {
});
}
// Upvote all comments containing "welcome to"
comments.find("li").each(function() {
if ($(this).find("span.comment-copy")[0].innerText.toLowerCase().indexOf("welcome to") >= 0) {
// Click the "up" triangle
let upButtons = $(this).find("a.comment-up");
if (upButtons.length > 0) {
upButtons[0].click();
if (hasUpvotePrivilege) {
// Upvote all comments containing "welcome to"
comments.find("li").each(function() {
if ($(this).find("span.comment-copy")[0].innerText.toLowerCase().indexOf("welcome to") >= 0) {
// Click the "up" triangle
let upButtons = $(this).find("a.comment-up");
if (upButtons.length > 0) {
upButtons[0].click();
}
}
}
});
});
}
// You can't flag without 15 rep
if (myReputation < 15)
return;
if (myReputation >= 100 && !downvoted) {
// Downvote
if (shouldDownvote) {
// Downvote (not when the post is already on -3 or lower, to be slightly more welcoming)
$.post({
url: "https://" + document.location.host + "/posts/" + postID + "/vote/3", // 3 = downvote
data: "fkey=" + fkey,
success: function () {
// TODO: set downvote button color
// NICETOHAVE: set downvote button color
console.log("Downvote cast.");
},
error: function (jqXHR, textStatus, errorThrown) {
@ -155,14 +243,14 @@ function main(question) {
});
}
if (!closed) {
// Flag/vote to close (doesn't matter for the API call)
if (shouldFlag) {
// Flag/vote to close (which one doesn't matter for the API call)
$.post({
url: "https://" + document.location.host + "/flags/questions/" + postID + "/close/add",
data: "fkey=" + fkey + "&closeReasonId=OffTopic&closeAsOffTopicReasonId=" + (window.location.host === "softwarerecs.stackexchange.com" ||
window.location.host === "hardwarerecs.stackexchange.com" ? "1" : "8"),
success: function () {
// TODO: update close vote count
// NICETOHAVE: update close vote count
console.log("Close flag/vote cast.");
},
error: function (jqXHR, textStatus, errorThrown) {
@ -170,14 +258,14 @@ function main(question) {
console.log("Error: " + textStatus + " " + errorThrown);
}
});
} else if (myReputation >= 20000 || isModerator) {
} else if (shouldVoteToDelete) {
// Delete vote
// TODO, at least for non-moderators: only if score <= -3, maybe also if myReputation >= 10000 and question age >= 48 hours
// NICETOHAVE: maybe also if myReputation >= 10000 and question age >= 48 hours
$.post({
url: "https://" + document.location.host + "/posts/" + postID + "/vote/10", // 10 = delete
data: "fkey=" + fkey,
success: function () {
// TODO: update delete vote count
// NICETOHAVE: update delete vote count
console.log("Delete vote cast.");
},
error: function (jqXHR, textStatus, errorThrown) {
@ -187,8 +275,7 @@ function main(question) {
});
}
// Reload page; this is less elegant than waiting for all POST calls but it works.
window.setTimeout(() => window.location.reload(false), 800);
});
// Reload page; this is less elegant than waiting for all POST calls, but it works.
window.setTimeout(() => window.location.reload(false), 1000);
};
}