2018-11-16 19:12:22 +00:00
// ==UserScript==
// @name 'Saviour' of Lost Souls
// @namespace https://github.com/Glorfindel83/
2019-05-02 18:05:16 +00:00
// @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.
2018-11-16 19:12:22 +00:00
// @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
2019-05-02 18:05:16 +00:00
// @version 0.5.2
2018-11-16 19:12:22 +00:00
// @match *://meta.stackexchange.com/questions/*
2019-01-06 17:23:15 +00:00
// @match *://meta.stackoverflow.com/questions/*
2019-04-01 13:52:59 +00:00
// @match *://softwarerecs.stackexchange.com/questions/*
2019-04-11 07:05:23 +00:00
// @match *://softwarerecs.stackexchange.com/review/first-posts*
2019-04-10 21:00:18 +00:00
// @match *://hardwarerecs.stackexchange.com/questions/*
2019-04-11 07:05:23 +00:00
// @match *://hardwarerecs.stackexchange.com/review/first-posts*
2018-12-27 08:20:02 +00:00
// @exclude *://meta.stackexchange.com/questions/ask
2019-01-06 17:23:15 +00:00
// @exclude *://meta.stackoverflow.com/questions/ask
2019-04-01 13:52:59 +00:00
// @exclude *://softwarerecs.stackexchange.com/questions/ask
2019-04-10 21:00:18 +00:00
// @exclude *://hardwarerecs.stackexchange.com/questions/ask
2018-11-16 19:12:22 +00:00
// @grant none
2019-04-10 21:00:18 +00:00
// @require https://gist.github.com/raw/2625891/waitForKeyElements.js
2018-11-16 19:12:22 +00:00
// ==/UserScript==
2019-04-10 21:00:18 +00:00
/* global $, waitForKeyElements */
2018-11-16 19:12:22 +00:00
2019-01-15 04:23:05 +00:00
( function ( $ ) {
2018-11-16 19:12:22 +00:00
"use strict" ;
2019-04-10 21:00:18 +00:00
// Find question (works when on Q&A page)
2019-01-06 17:23:15 +00:00
let question = $ ( '#question' ) ;
2019-04-10 21:00:18 +00:00
if ( question . length == 0 )
return ;
main ( question ) ;
} ) ( window . jQuery ) ;
2019-01-15 04:23:05 +00:00
2019-04-10 21:00:18 +00:00
// Wait for question (works when in review queue)
waitForKeyElements ( 'div.review-content div.question' , function ( jNode ) {
main ( jNode ) ;
} ) ;
function main ( question ) {
console . log ( question )
2019-01-06 17:23:15 +00:00
// Check if author is likely to be a lost soul
2019-04-10 21:00:18 +00:00
let owner = question . find ( 'div.post-signature.owner' ) ;
2019-01-06 17:27:20 +00:00
if ( owner . length == 0 )
2019-01-06 17:23:15 +00:00
// happens with Community Wiki posts
2018-11-16 19:12:22 +00:00
return ;
2019-01-06 17:23:15 +00:00
let reputation = owner . find ( 'span.reputation-score' ) [ 0 ] . innerText ;
2019-01-15 04:23:05 +00:00
if ( reputation === "1" ) {
// Do nothing: 1 rep qualifies for a lost soul
2019-01-06 17:23:15 +00:00
} else {
2019-01-15 18:23:27 +00:00
// Child meta sites require some reputation to post a question, so we need other rules:
2019-01-06 17:23:15 +00:00
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 ;
2018-11-16 19:12:22 +00:00
}
// My reputation; you need 5 reputation to comment
let myReputation = parseInt ( $ ( 'a.my-profile div.-rep' ) [ 0 ] . innerText . replace ( /,/g , '' ) ) ;
if ( myReputation < 5 ) {
return ;
2018-11-25 12:08:31 +00:00
}
let isModerator = $ ( "a.js-mod-inbox-button" ) . length > 0 ;
2019-01-15 04:23:05 +00:00
2018-11-16 19:12:22 +00:00
// Add post menu button
let menu = question . find ( 'div.post-menu' ) ;
menu . append ( $ ( '<span class="lsep">|</span>' ) ) ;
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 ;
2019-01-15 04:23:05 +00:00
2018-11-16 19:12:22 +00:00
// 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' ;
2019-01-15 04:23:05 +00:00
2018-11-16 19:12:22 +00:00
// Prepare votes/comments
let postID = parseInt ( question . attr ( 'data-questionid' ) ) ;
console . log ( 'Lost soul #' + postID ) ;
let fkey = window . localStorage [ "se:fkey" ] . split ( "," ) [ 0 ] ;
2019-01-15 04:23:05 +00:00
2018-11-16 19:12:22 +00:00
// 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 ;
2019-04-01 13:52:59 +00:00
let comment = window . location . host === "softwarerecs.stackexchange.com"
? ( "Hi " + author + ", welcome to [softwarerecs.se]! " +
2019-05-02 18:05:16 +00:00
"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)." )
2019-04-10 21:00:18 +00:00
: window . location . host === "hardwarerecs.stackexchange.com"
? ( "Hi " + author + ", welcome to [hardwarerecs.se]! " +
2019-05-02 18:05:16 +00:00
"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)." )
2019-04-01 13:52:59 +00:00
: ( "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." ) ;
2018-11-16 19:12:22 +00:00
$ . 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 ) ;
}
} ) ;
}
2019-01-15 11:11:08 +00:00
// 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 ( ) ;
}
}
} ) ;
2019-01-15 18:25:27 +00:00
2018-11-16 19:12:22 +00:00
// You can't flag without 15 rep
if ( myReputation < 15 )
2019-01-15 04:23:05 +00:00
return ;
2018-11-16 19:12:22 +00:00
if ( myReputation >= 100 && ! downvoted ) {
// Downvote
$ . post ( {
url : "https://" + document . location . host + "/posts/" + postID + "/vote/3" , // 3 = downvote
data : "fkey=" + fkey ,
success : function ( ) {
2018-11-25 12:08:31 +00:00
// TODO: set downvote button color
2018-11-16 19:12:22 +00:00
console . log ( "Downvote cast." ) ;
} ,
error : function ( jqXHR , textStatus , errorThrown ) {
window . alert ( "An error occurred, please try again later." ) ;
console . log ( "Error: " + textStatus + " " + errorThrown ) ;
}
} ) ;
}
2019-01-15 04:23:05 +00:00
2018-11-16 19:12:22 +00:00
if ( ! closed ) {
// Flag/vote to close (doesn't matter for the API call)
$ . post ( {
url : "https://" + document . location . host + "/flags/questions/" + postID + "/close/add" ,
2019-04-10 21:00:18 +00:00
data : "fkey=" + fkey + "&closeReasonId=OffTopic&closeAsOffTopicReasonId=" + ( window . location . host === "softwarerecs.stackexchange.com" ||
window . location . host === "hardwarerecs.stackexchange.com" ? "1" : "8" ) ,
2018-11-16 19:12:22 +00:00
success : function ( ) {
2018-11-25 12:08:31 +00:00
// TODO: update close vote count
2018-11-16 19:12:22 +00:00
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 ) ;
}
} ) ;
2018-11-25 12:08:31 +00:00
} else if ( myReputation >= 20000 || isModerator ) {
2018-11-16 19:12:22 +00:00
// Delete vote
2018-11-25 12:08:31 +00:00
// TODO, at least for non-moderators: only if score <= -3, maybe also if myReputation >= 10000 and question age >= 48 hours
2018-11-16 19:12:22 +00:00
$ . post ( {
url : "https://" + document . location . host + "/posts/" + postID + "/vote/10" , // 10 = delete
data : "fkey=" + fkey ,
success : function ( ) {
2018-11-25 12:08:31 +00:00
// TODO: update delete vote count
2018-11-16 19:12:22 +00:00
console . log ( "Delete vote cast." ) ;
} ,
error : function ( jqXHR , textStatus , errorThrown ) {
window . alert ( "An error occurred, please try again later." ) ;
console . log ( "Error: " + textStatus + " " + errorThrown ) ;
}
} ) ;
}
2019-01-15 04:23:05 +00:00
2019-01-15 18:23:27 +00:00
// Reload page; this is less elegant than waiting for all POST calls but it works.
2019-01-15 06:33:01 +00:00
window . setTimeout ( ( ) => window . location . reload ( false ) , 800 ) ;
2019-01-15 04:23:05 +00:00
} ) ;
2019-04-10 21:00:18 +00:00
}
2019-05-02 18:05:16 +00:00