'Saviour' of Lost Souls v1.0: Stacks design & option to post comment anyway
This commit is contained in:
parent
cc558ea804
commit
ab5ef30ae4
|
@ -5,7 +5,7 @@
|
||||||
// @author Glorfindel
|
// @author Glorfindel
|
||||||
// @updateURL https://raw.githubusercontent.com/Glorfindel83/SE-Userscripts/master/saviour-of-lost-souls/saviour-of-lost-souls.user.js
|
// @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
|
// @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.stackexchange.com/questions/*
|
||||||
// @match *://meta.stackoverflow.com/questions/*
|
// @match *://meta.stackoverflow.com/questions/*
|
||||||
// @match *://softwarerecs.stackexchange.com/questions/*
|
// @match *://softwarerecs.stackexchange.com/questions/*
|
||||||
|
@ -38,7 +38,6 @@ waitForKeyElements('div.review-content div.question', function(jNode) {
|
||||||
});
|
});
|
||||||
|
|
||||||
function main(question) {
|
function main(question) {
|
||||||
console.log(question)
|
|
||||||
// Check if author is likely to be a lost soul
|
// Check if author is likely to be a lost soul
|
||||||
let owner = question.find('div.post-signature.owner');
|
let owner = question.find('div.post-signature.owner');
|
||||||
if (owner.length == 0)
|
if (owner.length == 0)
|
||||||
|
@ -57,12 +56,51 @@ function main(question) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// My reputation; you need 5 reputation to comment
|
// Which site?
|
||||||
|
let isMetaSE = location.host == 'meta.stackexchange.com';
|
||||||
|
let isBeta = location.host == 'hardwarerecs.stackexchange.com';
|
||||||
|
|
||||||
|
// My reputation
|
||||||
let myReputation = parseInt($('a.my-profile div.-rep')[0].innerText.replace(/,/g, ''));
|
let myReputation = parseInt($('a.my-profile div.-rep')[0].innerText.replace(/,/g, ''));
|
||||||
if (myReputation < 5) {
|
let hasCommentPrivilege = myReputation >= (isMetaSE ? 5 : 50);
|
||||||
return;
|
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;
|
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
|
// Add post menu button
|
||||||
let menu = question.find('div.post-menu');
|
let menu = question.find('div.post-menu');
|
||||||
|
@ -70,31 +108,83 @@ function main(question) {
|
||||||
let button = $('<a href="#" title="down-/close-/delete vote and post a welcoming comment">lost soul</a>');
|
let button = $('<a href="#" title="down-/close-/delete vote and post a welcoming comment">lost soul</a>');
|
||||||
menu.append(button);
|
menu.append(button);
|
||||||
button.click(function() {
|
button.click(function() {
|
||||||
if (!confirm('Are you sure you want to down-/close-/delete vote and post a welcoming comment?'))
|
// Generate HTML for dialog
|
||||||
return;
|
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">
|
||||||
// Downvoted?
|
<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">
|
||||||
let downvoted = question.find('a.vote-down-on').length > 0;
|
<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
|
||||||
// Closed?
|
</h1>`;
|
||||||
let status = $('div.question-status h2 b');
|
html += `
|
||||||
let statusText = status.length > 0 ? status[0].innerText : '';
|
<div class="grid--cell">
|
||||||
let closed = statusText == 'marked' || statusText == 'put on hold' || statusText == 'closed';
|
<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
|
// Prepare votes/comments
|
||||||
let postID = parseInt(question.attr('data-questionid'));
|
let postID = parseInt(question.attr('data-questionid'));
|
||||||
console.log('Lost soul #' + postID);
|
|
||||||
let fkey = window.localStorage["se:fkey"].split(",")[0];
|
let fkey = window.localStorage["se:fkey"].split(",")[0];
|
||||||
|
|
||||||
// Is there any comment not by the author?
|
if (shouldComment || $("#sols-postCommentAnyway").prop("checked")) {
|
||||||
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) {
|
|
||||||
// Post comment
|
// Post comment
|
||||||
let author = owner.find('div.user-details a')[0].innerText;
|
let author = owner.find('div.user-details a')[0].innerText;
|
||||||
|
|
||||||
|
@ -124,28 +214,26 @@ function main(question) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upvote all comments containing "welcome to"
|
if (hasUpvotePrivilege) {
|
||||||
comments.find("li").each(function() {
|
// Upvote all comments containing "welcome to"
|
||||||
if ($(this).find("span.comment-copy")[0].innerText.toLowerCase().indexOf("welcome to") >= 0) {
|
comments.find("li").each(function() {
|
||||||
// Click the "up" triangle
|
if ($(this).find("span.comment-copy")[0].innerText.toLowerCase().indexOf("welcome to") >= 0) {
|
||||||
let upButtons = $(this).find("a.comment-up");
|
// Click the "up" triangle
|
||||||
if (upButtons.length > 0) {
|
let upButtons = $(this).find("a.comment-up");
|
||||||
upButtons[0].click();
|
if (upButtons.length > 0) {
|
||||||
|
upButtons[0].click();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// You can't flag without 15 rep
|
if (shouldDownvote) {
|
||||||
if (myReputation < 15)
|
// Downvote (not when the post is already on -3 or lower, to be slightly more welcoming)
|
||||||
return;
|
|
||||||
|
|
||||||
if (myReputation >= 100 && !downvoted) {
|
|
||||||
// Downvote
|
|
||||||
$.post({
|
$.post({
|
||||||
url: "https://" + document.location.host + "/posts/" + postID + "/vote/3", // 3 = downvote
|
url: "https://" + document.location.host + "/posts/" + postID + "/vote/3", // 3 = downvote
|
||||||
data: "fkey=" + fkey,
|
data: "fkey=" + fkey,
|
||||||
success: function () {
|
success: function () {
|
||||||
// TODO: set downvote button color
|
// NICETOHAVE: set downvote button color
|
||||||
console.log("Downvote cast.");
|
console.log("Downvote cast.");
|
||||||
},
|
},
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
@ -155,14 +243,14 @@ function main(question) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!closed) {
|
if (shouldFlag) {
|
||||||
// Flag/vote to close (doesn't matter for the API call)
|
// Flag/vote to close (which one doesn't matter for the API call)
|
||||||
$.post({
|
$.post({
|
||||||
url: "https://" + document.location.host + "/flags/questions/" + postID + "/close/add",
|
url: "https://" + document.location.host + "/flags/questions/" + postID + "/close/add",
|
||||||
data: "fkey=" + fkey + "&closeReasonId=OffTopic&closeAsOffTopicReasonId=" + (window.location.host === "softwarerecs.stackexchange.com" ||
|
data: "fkey=" + fkey + "&closeReasonId=OffTopic&closeAsOffTopicReasonId=" + (window.location.host === "softwarerecs.stackexchange.com" ||
|
||||||
window.location.host === "hardwarerecs.stackexchange.com" ? "1" : "8"),
|
window.location.host === "hardwarerecs.stackexchange.com" ? "1" : "8"),
|
||||||
success: function () {
|
success: function () {
|
||||||
// TODO: update close vote count
|
// NICETOHAVE: update close vote count
|
||||||
console.log("Close flag/vote cast.");
|
console.log("Close flag/vote cast.");
|
||||||
},
|
},
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
error: function (jqXHR, textStatus, errorThrown) {
|
||||||
|
@ -170,14 +258,14 @@ function main(question) {
|
||||||
console.log("Error: " + textStatus + " " + errorThrown);
|
console.log("Error: " + textStatus + " " + errorThrown);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (myReputation >= 20000 || isModerator) {
|
} else if (shouldVoteToDelete) {
|
||||||
// Delete vote
|
// 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({
|
$.post({
|
||||||
url: "https://" + document.location.host + "/posts/" + postID + "/vote/10", // 10 = delete
|
url: "https://" + document.location.host + "/posts/" + postID + "/vote/10", // 10 = delete
|
||||||
data: "fkey=" + fkey,
|
data: "fkey=" + fkey,
|
||||||
success: function () {
|
success: function () {
|
||||||
// TODO: update delete vote count
|
// NICETOHAVE: update delete vote count
|
||||||
console.log("Delete vote cast.");
|
console.log("Delete vote cast.");
|
||||||
},
|
},
|
||||||
error: function (jqXHR, textStatus, errorThrown) {
|
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.
|
// Reload page; this is less elegant than waiting for all POST calls, but it works.
|
||||||
window.setTimeout(() => window.location.reload(false), 800);
|
window.setTimeout(() => window.location.reload(false), 1000);
|
||||||
});
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue