Vote counter userscript Zach

From BlogNomic Wiki
Revision as of 17:37, 26 November 2021 by Josh (talk | contribs) (Created page with "The following is Zach's updates to the utility script. note, due to wikimedia formatting, you will need to go into edit mode before copying this script for it to work since...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

The following is Zach's updates to the utility script.

note, due to wikimedia formatting, you will need to go into edit mode before copying this script for it to work since media wiki counts two single quotes next to each other as the code for making italics.

Script

// ==UserScript== // @name BNScript: BlogNomic Utility Script // @namespace blognomic // @version 1.18.3.0 // @description Utility script for the game of BlogNomic, assisting admin in the automatic counting of votes and flagging of new comments. // @updateURL https://gist.githubusercontent.com/TheOnlyZac/d33ed740ef70eb16c65120ac8215f48d/raw/e9af4ca735d463b987accea2a9a23cf6e98bd665/Modified%2520BlogNomic%2520Utility%2520Script.js // @include https://*blognomic.com/ // @include https://*blognomic.com/archive/* // @include https://*blognomic.com/blognomic/archive/* // @include https://*blognomic.com/blognomic/ // @grant GM_getValue // @grant GM_setValue // @grant GM_log // @require http://code.jquery.com/jquery-1.11.2.min.js // ==/UserScript==

/* Updated script by Zack (TheOnlyZac#0269 on Discord)

  The original script is available here: https://wiki.blognomic.com/index.php?title=Vote_counter_userscript
  Get the latest version here: https://gist.githubusercontent.com/TheOnlyZac/d33ed740ef70eb16c65120ac8215f48d/raw/e9af4ca735d463b987accea2a9a23cf6e98bd665/Modified%2520BlogNomic%2520Utility%2520Script.js
  Changelog
  1.18.3 (18 Oct 2021)
  - Renamed "sk" to "withdraw"
  - Removed "Withdrawn" text from non-proposal votable matters
  1.18.2 (11 Oct 2021)
   - Implemented collapsable posts on the homepage
   - Minor tweaks and improvements
  1.18.1 (11 Oct 2021)
   - Refreshed settings menu
   - Added non-voters to voters list
  1.18.0 (10 Oct 2021)
   - Overhauled vote counting
   - Added voters list beneath vote total
  • /

inputs = document.getElementsByTagName("input"); for (i = 0; i < inputs.length; ++i) {

   if (inputs[i].name == "notify_me") inputs[i].checked = false;

}

// This whole block from http://umkk.eu/wp-content/uploads/2009/10/json.js var JSON = JSON || {}; (

   function () {
       function f(n) {
           return n < 10 ? '0' + n : n;
       }
       if (typeof Date.prototype.toJSON !== 'function') {
           Date.prototype.toJSON = function (key) {
               return isFinite(this.valueOf()) ? this.getUTCFullYear() + '-' +
                   f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' +
                   f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' +
                   f(this.getUTCSeconds()) + 'Z' : null;
           };
           String.prototype.toJSON = Number.prototype.toJSON = Boolean.prototype.toJSON = function (key) {
               return this.valueOf();
           }
       }
       var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, gap, indent, meta = { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\' }, rep;
       function quote(string) {
           escapable.lastIndex = 0;
           return escapable.test(string) ? '"' +
               string.replace(escapable, function (a) {
                   var c = meta[a];
                   return typeof c === 'string' ? c : '\\u' +
                       ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
               }) + '"' : '"' + string + '"'
       }
       function str(key, holder) {
           var i, k, v, length, mind = gap, partial, value = holder[key];
           if (value && typeof value === 'object' &&
               typeof value.toJSON === 'function') {
               value = value.toJSON(key);
           }
           if (typeof rep === 'function') {
               value = rep.call(holder, key, value);
           }
           switch (typeof value) {
               case 'string':
                   return quote(value);
               case 'number':
                   return isFinite(value) ? String(value) : 'null';
               case 'boolean':
               case 'null':
                   return String(value);
               case 'object':
                   if (!value) {
                       return 'null';
                   }
                   gap += indent;
                   partial = [];
                   if (Object.prototype.toString.apply(value) === '[object Array]') {
                       length = value.length;
                       for (i = 0; i < length; i += 1) {
                           partial[i] = str(i, value) || 'null';
                       }
                       v = partial.length === 0 ? '[]' : gap ? '[\n' + gap +
                           partial.join(',\n' + gap) + '\n' + mind + ']' : '[' +
                           partial.join(',') + ']';
                       gap = mind;
                       return v;
                   }
                   if (rep && typeof rep === 'object') {
                       length = rep.length;
                       for (i = 0; i < length; i += 1) {
                           k = rep[i];
                           if (typeof k === 'string') {
                               v = str(k, value);
                               if (v) {
                                   partial.push(quote(k) + (gap ? ': ' : ':') + v);
                               }
                           }
                       }
                   }
                   else {
                       for (k in value) {
                           if (Object.hasOwnProperty.call(value, k)) {
                               v = str(k, value);
                               if (v) {
                                   partial.push(quote(k) + (gap ? ': ' : ':') + v);
                               }
                           }
                       }
                   }
                   v = partial.length === 0 ? '{}' : gap ? '{\n' + gap +
                       partial.join(',\n' + gap) + '\n' + mind + '}' : '{' +
                       partial.join(',') + '}';
                   gap = mind;
                   return v;
           }
       }
       if (typeof JSON.stringify !== 'function') {
           JSON.stringify = function (value, replacer, space) {
               var i;
               gap = ;
               indent = ;
               if (typeof space === 'number') {
                   for (i = 0; i < space; i += 1) {
                       indent += ' ';
                   }
               }
               else if (typeof space === 'string') {
                   indent = space;
               }
               rep = replacer;
               if (replacer && typeof replacer !== 'function' &&
                   (typeof replacer !== 'object' || typeof replacer.length !==
                       'number')) {
                   throw new Error('JSON.stringify');
               }
               return str(, { : value });
           }
       }
       if (typeof JSON.parse !== 'function') {
           JSON.parse = function (text, reviver) {
               var j;
               function walk(holder, key) {
                   var k, v, value = holder[key];
                   if (value && typeof value === 'object') {
                       for (k in value) {
                           if (Object.hasOwnProperty.call(value, k)) {
                               v = walk(value, k);
                               if (v !== undefined) {
                                   value[k] = v;
                               }
                               else {
                                   delete value[k];
                               }
                           }
                       }
                   }
                   return reviver.call(holder, key, value);
               }
               cx.lastIndex = 0;
               if (cx.test(text)) {
                   text = text.replace(cx, function (a) {
                       return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                   });
               }
               if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ))) {
                   j = eval('(' + text + ')');
                   return typeof reviver === 'function' ? walk({ : j }, ) : j;
               }
               throw new SyntaxError('JSON.parse');
           }
       }
   }());

// End borrowed code

var settings;

function getComments(text) {

   return parseInt(text.replace(/[^0-9]/g, ))

}

function set(k, v) { // hack

   window.setTimeout(function () {
       GM_setValue(k, v)
   }, 0)

}

function get(k, v, callback) { // hack

   window.setTimeout(function () {
       callback(GM_getValue(k, v))
   }, 0)

}

function collapsePosts() {

   $('.post').each(function(i) {
       let $this = $(this);
       if ($this.hasClass('stickypost')) return; // ignore stickied posts
       let $title = $this.find('.post-title');
       let $body = $this.find('.post-body');
       let $footer = $this.find('.post-footer');
       let $btn = $(document.createElement('div')); // create expand/collapse button
       $btn.addClass('collapse-btn');
       $btn.text("◂");
       $btn.css({
           "float": "right",
           "width": "20px",
           "height": "20px",
           "text-align": "center",
           "color": "black",
           "background-color": "#f0f0f0",
           "border": "1px solid black",
           "border-radius": "2px",
           "user-select": "none",
           "cursor": "pointer"
       });
       $btn.content = $body;
       let $newFooter = $footer.detach();
       $this.append($newFooter); // move footer out of post body
       $body.hide(); // hide post body by default
       $this.prepend($btn) // add expand/collapse button
   });

}

/* Function that runs on the main blog page */ function mainPage() {

   if (settings['collapsePosts']) {
       collapsePosts();
   }
   $("<button type=\"button\">BNScript Settings</button>").prependTo("#sidebar2").click(settings_query);
   $("a[title='Comments']").each(function (i) {
       var link = this;
       get(this.href, false, function (old_comments) {
           let num_comments = getComments(link.text)
           let num_new_comments = num_comments - (old_comments || 0)
           if (settings['showNew'] && ((!old_comments && num_comments > 0) || num_comments > old_comments)) {
               $(link).append(' (' + num_new_comments + ' new)')
           }
           $(link).click(function () {
               set(link.href, num_comments)
           })
           if (settings['showNew']) {
               if (num_new_comments > 0) {
                   $('#header').append('<a href="' + link.href + '" style="color: ' + settings['colorNew'] + '">' + (num_new_comments) + '</a> ')
               } else {
                   $('#header').append('0 ')
               }
           }
       })
   })

}

/* Function that runs on archive (post) pages */ function archivePage() {

   $("a[title='Comments']").each(function (i) {
       set(this.href, getComments(this.text));
   });
   let votes = {};
   let proposer = $("p[class=post-footer]").children().filter("em").children().filter("a").html();
   let isProposal = /^Proposal\:/.test($("a[class^=blogpostlink]").html());
   let quorum;
   quorum = $("#PlayerList").next().html();
   quorum = /\D*(\d+)\D*(\d+)/.exec(quorum);
   let numPlayers = parseInt(quorum[1]);
   quorum = parseInt(quorum[2]);
   GM_log('Number of players: ' + numPlayers);
   GM_log('Quorum: ' + quorum);
   let failQuorum = quorum - 1 + numPlayers % 2;
   GM_log('Failure quorum: ' + failQuorum);
   $("div[class='comment']").
       each(
           function (i) {
               let comment = this;
               let name = $(comment).children().filter('h3').children().
                   filter('a').html();
                   let cbody = $(comment).children().filter('div.commentbody');
               if (settings['debug']) {
                   GM_log('In vote-counter: voter #' + i + ': ' + name);
               }
               $(cbody).find("img").each(
                   function (j) {
                       let cb = this;
                       let vote = cb.getAttribute('alt');
                       if (settings['debug']) {
                           GM_log('In vote-counter: vote #' + j + ': ' +
                               vote);
                       }
                       if (vote == 'for' || vote == 'against' ||
                           vote == 'imperial' ||
                           (vote == 'veto' && isProposal && settings[emperorVote] !=  && name == settings[emperorVote]))
                           votes[name] = vote;
                       else if (settings['debug'])
                           GM_log('Vote not counted');
                   });
           });
   /* Get the list of players from the sidebar and store it as keys in the players object */
   let players = {};
   $("#PlayerList > a").each(function (i) {
       players[$(this).text().replace(/\*$/, "")] = true;
   });
   /* Initialize the vote counts to 0 */
   let votesFor = 0;
   let forVoters = [];
   let votesAgainst = 0;
   let againstVoters = [];
   let votesImperial = 0;
   let imperialVoters = [];
   let nonVoters = Object.keys(players); // players will be removed as their vote is counted
   let emperorVote = ;
   let veto = false;
   let sk = false;
   /* Get the logged-in player's name */
   var yourName = $('#sidebar2 .sidebar-title').first().text();
   yourName = $.trim(yourName);
   console.log(yourName);
   let yourVoteInvalid = false;
   /* If the proposer hasn't voted, their vote defaults to FOR */
   if (votes[proposer] == undefined)
       votes[proposer] = 'for';
   if (settings['debug']) {
       GM_log('In vote-counter: Vote count: ' + JSON.stringify(votes));
       GM_log('In vote-counter: Proposer: ' + proposer);
       GM_log('In vote-counter: isProposal: ' + isProposal);
   }
   /* Tally up everyone's votes */
   for (player in votes) {
       if (!players[player])
           continue; // not an active player, skip their vote
       
       nonVoters.splice(nonVoters.indexOf(player), 1);
       
       if (settings[emperorVote] !=  && player == settings['emperor']) {
           emperorVote = votes[player];
       }
       switch (votes[player]) {
           case 'for':
               forVoters.push(player);
               votesFor++;
               break;
           case 'against':
               if (player == proposer) sk = true;
               againstVoters.push(player);
               votesAgainst++;
               break;
           case 'imperial':
               if (emperorVote == 'imperial' && player != settings['emperor']) {
                   console.log(player);
                   console.log('invalid imperial vote');
                   console.log(player == yourName);
                   if (player == yourName) {
                       yourVoteInvalid = true;
                   }
                   break;
               }
               imperialVoters.push(player);
               votesImperial++;
               break;
           case 'veto':
               if (player == settings['emperor']) veto = true;
               break;
           default:
               break;
       }
   }
   if (settings['debug']) {
       GM_log('In vote-counter: emperor\'s vote: ' + emperorVote);
       GM_log('In vote-counter: vote numbers: ' + votesFor + ' ' +
           votesAgainst + ' ' + votesImperial);
       GM_log('In vote-counter: veto/s-k: ' + veto + '/' + sk);
   }
   if (emperorVote == 'imperial') {
       if (votesFor > votesAgainst) {
           emperorVote = 'for';
           votesFor++;
           votesImperial--;
       } else {
           emperorVote = 'against';
           votesAgainst++;
           votesImperial--;
       }
   }
   let forAdj = votesFor
   let againstAdj = votesAgainst
   let imperialAdj = votesImperial;
   if (emperorVote == 'for') {
       forAdj += imperialAdj;
       imperialAdj = 0;
   }
   if (emperorVote == 'against') {
       againstAdj += imperialAdj;
       imperialAdj = 0;
   }
   /* Generate total vote count text to display on the page */
   // Create css style string for span element
   let styleString = 'display: block; text-align: left; margin: 4px; padding: 2px; ';
   let statusColor = '#000000';
   if (forAdj >= quorum)
       statusColor = settings['colorPassed'];
   if (againstAdj >= failQuorum || veto || sk)
       statusColor = settings['colorFailed'];
   let postPreamble = [];
   if (settings['showTotal']) {
       postPreamble.push(``);
       // Add prefix if proposal was vetoed or withdrawn
       if (veto)
           postPreamble.push('Vetoed; ');
       else if (sk && isProposal)
           postPreamble.push('Withdrawn; ');
       postPreamble.push(`Votes: ${forAdj}-${againstAdj}`);
       if (emperorVote !=  && votesImperial != 0) {
           postPreamble.push(` (${votesFor}-${votesAgainst}-${votesImperial})`);
       }
       else {
           postPreamble.push(
               (imperialAdj != 0 ? (`-${imperialAdj}`) : )
           )
       }
       postPreamble.push('');
   }
   // Add list of who voted what
   if (settings['showForVotes'] && forVoters.length > 0) {
       postPreamble.push(`FOR: ${forVoters.join(', ')}`)
   }
   if (settings['showAgainstVotes'] && againstVoters.length > 0) {
       postPreamble.push(`AGAINST: ${againstVoters.join(', ')}`)
   }
   if (settings['showDefVotes'] && imperialVoters.length > 0) {
       postPreamble.push(`DEF: ${imperialVoters.join(', ')}`)
   }
   if (settings['showNonVotes'] && nonVoters.length > 0) {
       postPreamble.push(`N/A: ${nonVoters.join(', ')}`)
   }
   if (yourVoteInvalid) {
       postPreamble.push(`Your vote on this proposal is invalid.`)
   }
   postPreamble = postPreamble.join();
   /* Add the vote total to the page only if the showTotal setting is enabled */
   $("#main2").prepend(postPreamble);
   /* List everyone's votes below the vote total */
   console.log(votes);

}

/* Manage settings and show settings popup */ var settings_default = {

   'emperor': ,
   'collapsePosts': false,
   'showTotal': true,
   'showForVotes': true,
   'showAgainstVotes': true,
   'showDefVotes': true,
   'showNonVotes': true,
   'colorPassed': '#0f0',
   'colorFailed': '#f00',
   'colorDef': '#00f',
   'colornonVote': '#000',
   'showNew': true,
   'colorNew': '#f00',
   'debug': false

};

var form_data = [

'

Blognomic Utility Script

', '

Updated version by Zack (v1.18.2.0)

', '


',

   'Current Emperor: <input type="text" name="emperor">
', 'Collapse posts on main page: <input type="checkbox" name="collapsePosts">
',

'


',

   'Show vote totals: <input type="checkbox" name="showTotal">
', 'Show FOR voters: <input type="checkbox" name="showForVotes">
', 'Show AGAINST voters: <input type="checkbox" name="showAgainstVotes">
', 'Show DEF voters: <input type="checkbox" name="showDefVotes">
', 'Show non-voters: <input type="checkbox" name="showNonVotes">
',

'


',

   `Color of FOR counts: <input type="color" name="colorPassed">
`, `Color of AGAINST counts: <input type="color" name="colorFailed">
`, `Color of DEF counts: <input type="color" name="colorDef">
`, `Color of non-voters: <input type="color" name="colornonVote">
`,

'


',

   'Show new comments: <input type="checkbox" name="showNew">
', `Color of new comments: <input type="color" name="colorNew">
`,

'',

   'Debug mode: <input type="checkbox" name="debug">
',

'<a href="https://twitter.com/messages/2844746861-2844746861?text=BNScript Feedback:" target="_blank">Send Feedback / Report a Bug</a>
',

   '<button type="button">Save</button>'
   

].join();

function settings_query(event) {

   let $body = $(document.body);
   let bgcolor = $body.css("background-color");

let $backdrop = $('

');

   let $form = $('<form id="settings-popup"></form>');
   $backdrop.css({
       'display': 'flex',
       'flex-direction': 'column',
       'justify-content': 'center',
       'align-items': 'center',
       'position': 'fixed',
       'margin': '0',
       'padding': '0',
       'width': '100%',
       'height': '100%',
       'background-color': 'rgba(0, 0, 0, 0.7)',
       'z-index': '999'
   })
   $form.css({
       'padding': '7px',
       'background-color': bgcolor,
       'border': '2px solid white',
       'border-radius': '7px'
   })
   GM_log('At start: ' + JSON.stringify(settings));
   
   $form.submit(function () {
       settings_save($(this));
       return false;
   });
   $form.append(form_data);
   $backdrop.append($form);
   $body.prepend($backdrop);
   $form.children().filter('[name=emperor]').attr('value', settings['emperor']);
   $form.children().filter('[name=collapsePosts]').attr('checked', settings['collapsePosts']);
   
   $form.children().filter('[name=showTotal]').attr('checked', settings['showTotal']);
   $form.children().filter('[name=showForVotes]').attr('checked', settings['showForVotes']);
   $form.children().filter('[name=showAgainstVotes]').attr('checked', settings['showAgainstVotes']);
   $form.children().filter('[name=showDefVotes]').attr('checked', settings['showDefVotes']);
   $form.children().filter('[name=showNonVotes]').attr('checked', settings['showNonVotes']);
   $form.children().filter('[name=colorPassed]').attr('value', settings['colorPassed']);
   $form.children().filter('[name=colorFailed]').attr('value', settings['colorFailed']);
   $form.children().filter('[name=colorDef]').attr('value', settings['colorDef']);
   $form.children().filter('[name=colornonVote]').attr('value', settings['colornonVote']);
   $form.children().filter('[name=showNew]').attr('checked', settings['showNew']);
   $form.children().filter('[name=colorNew]').attr('value', settings['colorNew']);
   $form.children().filter('[name=debug]').attr('checked', settings['debug']);
   $form.children().filter('button').click(function (event) {
       settings_save($(this).parent());
   });

}

function settings_save(form) {

   settings['emperor'] = form.children().filter('[name=emperor]').prop('value');
   settings['collapsePosts'] = form.children().filter('[name=collapsePosts]').is(':checked');
   settings['showTotal'] = form.children().filter('[name=showTotal]').is(':checked');
   settings['showForVotes'] = form.children().filter('[name=showForVotes]').is(':checked');
   settings['showAgainstVotes'] = form.children().filter('[name=showAgainstVotes]').is(':checked');
   settings['showDefVotes'] = form.children().filter('[name=showDefVotes]').is(':checked');
   settings['showNonVotes'] = form.children().filter('[name=showNonVotes]').is(':checked');
   settings['colorPassed'] = form.children().filter('[name=colorPassed]').prop('value');
   settings['colorFailed'] = form.children().filter('[name=colorFailed]').prop('value');
   settings['colorDef'] = form.children().filter('[name=colorDef]').prop('value');
   settings['colornonVote'] = form.children().filter('[name=colornonVote]').prop('value');
   settings['colorNew'] = form.children().filter('[name=colorNew]').prop('value');
   settings['showNew'] = form.children().filter('[name=showNew]').is(':checked');
   settings['debug'] = form.children().filter('[name=debug]').is(':checked');
   GM_log(JSON.stringify(settings));
   set("settings", JSON.stringify(settings));
   unsafeWindow.location.reload();

}

var setstr = GM_getValue("settings");

/* Run relevant function depending on whether you're on main or archive page */ $(document).ready(function () {

   if (setstr)
       settings = JSON.parse(setstr);
   else
       settings = settings_default;
   if (/^https?:\/\/(www\.)?blognomic.com\/(blognomic\/)?archive\/.*$/.
       test(location.href)) {
       archivePage();
   }
   else if (/^https?:\/\/(www\.)?blognomic.com\/(blognomic\/)?$/.
       test(location.href)) {
       mainPage();
   }

});

/* Handle clicking on collapse/uncollapse post button */ $(document).on('click', '.collapse-btn', function() {

   var $this = $(this);
   var $postBody = $this.parent().find('.post-body');
   $postBody.toggle('fast');
   if ($this.text() == '◂') $this.text('▾');
   else $this.text('◂');

});