// ==UserScript== // @name 'Saviour' of Lost Souls // @namespace https://github.com/Glorfindel83/ // @description Adds a shortcut to down-/close-/delete vote and post a welcoming comment to Lost Souls on Meta Stack Exchange and some other sites. // @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 // @match *://meta.stackexchange.com/questions/* // @match *://meta.stackoverflow.com/questions/* // @match *://softwarerecs.stackexchange.com/questions/* // @match *://softwarerecs.stackexchange.com/review/first-posts* // @match *://hardwarerecs.stackexchange.com/questions/* // @match *://hardwarerecs.stackexchange.com/review/first-posts* // @exclude *://meta.stackexchange.com/questions/ask // @exclude *://meta.stackoverflow.com/questions/ask // @exclude *://softwarerecs.stackexchange.com/questions/ask // @exclude *://hardwarerecs.stackexchange.com/questions/ask // @grant none // @require https://gist.github.com/raw/2625891/waitForKeyElements.js // ==/UserScript== /* global $, waitForKeyElements */ (function ($) { "use strict"; // Find question (works when on Q&A page) let question = $('#question'); if (question.length == 0) return; main(question); })(window.jQuery); // Wait for question (works when in review queue) waitForKeyElements('div.review-content div.question', function(jNode) { main(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) // happens with Community Wiki posts return; let reputation = owner.find('span.reputation-score')[0].innerText; if (reputation === "1") { // Do nothing: 1 rep qualifies for a lost soul } else { // Child meta sites require some reputation to post a question, so we need other rules: let isNewContributor = owner.find('span.js-new-contributor-label').length > 0; let hasLowReputation = reputation <= 101; // association bonus let negativeQuestionScore = parseInt(question.find('div.js-vote-count').text()) < 0; let numberOfReasons = (isNewContributor ? 1 : 0) + (hasLowReputation ? 1 : 0) + (negativeQuestionScore ? 1 : 0); if (numberOfReasons < 2) return; } // My reputation; you need 5 reputation to comment let myReputation = parseInt($('a.my-profile div.-rep')[0].innerText.replace(/,/g, '')); if (myReputation < 5) { return; } let isModerator = $("a.js-mod-inbox-button").length > 0; // Add post menu button let menu = question.find('div.post-menu'); menu.append($('|')); let button = $('lost soul'); 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'; // 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) { // 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). " + "If you think you can [edit] it to become on-topic, please have a look at the [question quality guidelines](https://softwarerecs.meta.stackexchange.com/q/336/23377).") : window.location.host === "hardwarerecs.stackexchange.com" ? ("Hi " + author + ", welcome to [hardwarerecs.se]! " + "This question does not appear to be about hardware recommendations, within [the scope defined on meta](https://hardwarerecs.meta.stackexchange.com/questions/tagged/scope) and in the [help center](/help/on-topic)." + "If you think you can [edit] it to become on-topic, please have a look at the [question quality guidelines](https://hardwarerecs.meta.stackexchange.com/q/205/4495).") : ("Hi " + author + ", welcome to Meta! " + "I'm not sure which search brought you here but the problem you describe will not be answered on this specific site. " + "To get an answer from users that have the expertise about the topic of your question you'll have to find and then re-post on the [proper site](https://stackexchange.com/sites). " + "Check [How do I ask a good question](/help/how-to-ask) and [What is on topic](/help/on-topic) on the *target* site to make sure your post is in good shape. " + "Your question is definitely off-topic on [Meta](/help/whats-meta) and is better deleted here."); $.post({ url: "https://" + document.location.host + "/posts/" + postID + "/comments", data: "fkey=" + fkey + "&comment=" + encodeURI(comment), success: function () { console.log("Comment posted."); }, error: function (jqXHR, textStatus, errorThrown) { window.alert("An error occurred, please try again later."); console.log("Error: " + textStatus + " " + errorThrown); } }); } // 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 $.post({ url: "https://" + document.location.host + "/posts/" + postID + "/vote/3", // 3 = downvote data: "fkey=" + fkey, success: function () { // TODO: set downvote button color console.log("Downvote cast."); }, error: function (jqXHR, textStatus, errorThrown) { window.alert("An error occurred, please try again later."); console.log("Error: " + textStatus + " " + errorThrown); } }); } if (!closed) { // Flag/vote to close (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 console.log("Close flag/vote cast."); }, error: function (jqXHR, textStatus, errorThrown) { window.alert("An error occurred, please try again later."); console.log("Error: " + textStatus + " " + errorThrown); } }); } else if (myReputation >= 20000 || isModerator) { // Delete vote // TODO, at least for non-moderators: only if score <= -3, 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 console.log("Delete vote cast."); }, error: function (jqXHR, textStatus, errorThrown) { window.alert("An error occurred, please try again later."); console.log("Error: " + textStatus + " " + errorThrown); } }); } // Reload page; this is less elegant than waiting for all POST calls but it works. window.setTimeout(() => window.location.reload(false), 800); }); }