Page 2 of 3

PostPosted: Tue Dec 05, 2017 1:47 pm
by Wolfram and Hart
PuppetMaster Login Script

Edit: Version 1.6. This version is slightly faster. NS can receive 25 requests every 30 seconds. Previous versions left a gap of 1.2 seconds between NS acknowledging a request, and sending the next one. As there is a short delay between sending request, and receiving acknowledgement, 25 requests were taking over 30 seconds. This version keeps an array of timestamps that the requests were received by NS. This allows request 26 to be sent exactly 30 seconds after request 1, request 27 to be sent exactly 30 seconds after request 2 etc.

Edit: Now on version 1.4. This one displays the region your puppet is in, and a count of any unread notices, TGs, RMB posts, and unanswered issues. It also puts your WA nation at the top of the screen (handy if you forgot where you left your WA!), followed by nations in the Rejected Realms (handy to see if your puppets got booted out of a region).


Edit: I've updated the code to version 1.2. This new version can (theoretically) be used for unlimited puppets. Login requests are suitably spaced apart to keep [v] satisfied!
The original version 1.0 was limited to a maximum of 25 puppets, and sent all requests at once. I've kept the original code in a spoiler in case anyone cares from an educational point of view.

What
Use this code to log in to a list of your puppets with the push of one button. Handy to stop them CTEing!

How
On your PC or laptop, open a text editor, like Notepad. Copy the code (below) into the editor.

Put your own email address in the code (place indicated by all the asterisks).

Put your own Nation names and passwords in the code (place indicated by all the asterisks).

REMEMBER: If you share your computer, then potentially someone else will have access to these passwords.

Save the file as “PuppetLogin.html”

Check the filename. If it saved as “PuppetLogin.html.txt” or similar, rename it to “PuppetLogin.html”.

When you open the file again, it should get opened in your browser by default. You should see a black page with “Wolfram & Hart” written across the top.

When you click the button, the code should send login requests to NS. NS then sends a response back. The code associated with the response gets displayed on your webpage.

Why
Yes, I know there's already code out there that you can download. But I don't like downloading things, and I like to know how things work. So I wrote code that needs no downloading, and is filled with explanatory comments.

HTML can be written in a simple text editor, and JavaScript can be embedded directly in HTML. Both will be recognised by your browser without the need for any other software or downloads. So they're what I used.

This is the first time I've properly tried to code HTML and JavaScript, so I've added comments at practically every line of code, explaining what it does. That also means users know exactly what they are getting.


Feedback
If you use this script, please drop [nation]Wolfram & Hart[/nation] a telegram. It would be interesting to know how widely this was used (if at all!). Any suggestions for improvements and enhancements are welcome.

Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart Puppet Login Script v1.0

     Version | Date        | Name           | Comment
     1.0     | 04 Dec 2017 | Wolfram & Hart | Created

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html 

     Don't use this code to log in more than 25 puppets in 30 seconds.
     (That would break NationStates API rules, as at the time of writing).
-->

<head>
  <!-- Set the title, visible in the browser tab -->
  <title>W&H Puppet Login</title>

  <!-- Set the colour scheme and format for the page -->
  <style>
    body {background-color: black;}
    div {
      text-align:center;
      color: white;
      font-family: times;
    }
  </style>
</head>

<body>
  <div>
    <!-- Display headers.  Use <span> tags to format the "W", "&", and "H" in "Wolfram & Hart". -->
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>Puppet Login</h3>

    <!-- Add a blank line -->
    <br>

    <!-- Add a button.  When clicked, this will run the JavaScript code -->
    <button type="button" onclick="fLoginAll()">Login</button>

    <!-- Add an empty paragraph, ready to display results -->
    <p id="result"></p>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>

    /* **********************CHANGE THIS TO USE YOUR OWN EMAIL ADDRESS********************** */
    /* NationStates require a 'UserAgent', so you can be contacted if something goes wrong */
    var strUserAgent = "myname@email.com"
    /* ************************************************************************************* */

    /* Tell NS the format of the password.  This code uses the unencrypted text password.
       (The one you type into the NationStates website).  NS allows you to use an encrypted
       password instead.  See this page for more details:
       https://www.nationstates.net/pages/api.html#nationapi-privateshards
    */
    var strPasswordType = "X-Password"

    /* Set a variable to record the nations processed.  This will update the html paragraph */
    var strMessage = "";

    /* ************CHANGE THIS TO INCLUDE UP TO 25 OF YOUR NATIONS AND PASSWORDS*********** */
    /* This function calls the fLogin function (below) for each nation / password pair.
       You can have up to 25 nations here without breaking NS API rules */
    function fLoginAll() {
      fLogin("Nation 1", "Password1")
      fLogin("Nation 2", "Password2")
      fLogin("Nation 3", "Password3")
      fLogin("Nation 4", "Password4")
    }
    /* ************************************************************************************* */

    /* This function uses AJAX to 'ping' (login) each nation, and records if the ping worked. */
    function fLogin(strNation, strPassword) {
      /* Create an object to send the login details to NS */
      var xhttp = new XMLHttpRequest();

      /* Set the object to update the html paragraph when it gets a response from NS */
      xhttp.onreadystatechange = function() {
        if (xhttp.readyState == 4) {
          strMessage = strMessage+"<br>" + strNation + ": " + xhttp.status;
          if (xhttp.status == 200) {
            strMessage = strMessage + ": OK";
          } else if (xhttp.status == 403) {
            strMessage = strMessage + ": Forbidden (incorrect password?)";
          } else if (xhttp.status == 404) {
            strMessage = strMessage + ": Not Found (nation doesn't exist?)";
          } else if (xhttp.status == 429) {
            strMessage = strMessage + ": Exceeding Rate Limit (max 25 nations every 30 seconds)";
          }
          document.getElementById("result").innerHTML = strMessage;
        }
      };

      /* Tell the object which nation URL to log in to. 
         Replace any space characters in the nation name (" ") with underlines ("_")
         Tell it the password.
         Tell it your contact details.
         Send the request to NS.
      */
      xhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?nation="+strNation.replace(" ", "_")+"&q=ping", true);
      xhttp.setRequestHeader(strPasswordType, strPassword)
      xhttp.setRequestHeader("User-Agent", strUserAgent)
      xhttp.send();
    }
  </script>
</body>
</html>
Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart Puppet Login Script v1.2

     Version | Date        | Name           | Comment
     1.0     | 04 Dec 2017 | Wolfram & Hart | Created
     1.2     | 16 Dec 2017 | Wolfram & Hart | Changes following Roavin's advice:
             |             |                | Put nation / password pairs into an array.
             |             |                | Included 'setTimeout()' in fLogin function
             |             |                | to leave suitable gap between logins.
             |             |                | Other minor changes.

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html 
-->

<head>
  <!-- Set the title, visible in the browser tab -->
  <title>W&H Puppet Login</title>

  <!-- Set the colour scheme and format for the page -->
  <style>
    body {background-color: black;}
    div {
      text-align:center;
      color: white;
      font-family: times;
    }
  </style>
</head>

<body>
  <div>
    <!-- Display headers.  Use <span> tags to format the "W", "&", and "H" in "Wolfram & Hart". -->
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>Puppet Login</h3>

    <!-- Add a blank line -->
    <br>

    <!-- Add a button.  When clicked, this will run the JavaScript function, "fMain" -->
    <button type="button" onclick="fMain()">Login</button>

    <!-- Add an empty paragraph, ready to display results -->
    <p id="result"></p>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>

    /* **********************CHANGE THIS TO USE YOUR OWN EMAIL ADDRESS********************** */
    /* NationStates require a 'UserAgent', so you can be contacted if something goes wrong   */
    var strUserAgent = "myname@email.com";
    /* ************************************************************************************* */

    /* ***************CHANGE THIS ARRAY TO INCLUDE YOUR NATIONS AND PASSWORDS*************** */
    var aobjNations = [
      {nation:"Nation 1", password:"Password1"},
      {nation:"Nation 2", password:"Password2"},
      {nation:"Nation 3", password:"Password3"},
      {nation:"Last Nation", password:"LastPassword"}
    ];
    /* ************************************************************************************* */

    /* Tell NS the format of the password.  This code uses the unencrypted text password.
       (The one you type into the NationStates website).  NS allows you to use an encrypted
       password instead.  See this page for more details:
       https://www.nationstates.net/pages/api.html#nationapi-privateshards
    */
    var strPasswordType = "X-Password";

    /* Set a variable to record the nations processed.  This will update the html paragraph. */
    var strMessage = "";

    /* The fMain function resets the 'results' paragraph to being blank.
       It then tells the code to login the first nation in the list (position 0)
    */
    function fMain() {
      strMessage = "";
      document.getElementById("result").innerHTML = "";
      fLogin(0);
    }

    /* The fLogin function uses AJAX to 'ping' (login) each nation, and records if the ping worked.
       intNation is a number indicating which nation in the array is being processed.
       A value of 0 indicates the first nation in the array.
    */
    function fLogin(intNation) {

      /* If there are still nations to be processed, then process them */
      if (intNation < aobjNations.length) {

        /* get the nation name & password from the list */
        var strNation = aobjNations[intNation].nation;
        var strPassword = aobjNations[intNation].password;

        /* Create an object to send the login details to NS */
        var objXhttp = new XMLHttpRequest();

        /* Set the object to update the html paragraph when it gets a response from NS
           When a response is received, update the paragraph.  Then wait 1200ms (1.2 seconds)
           before logging in the next nation.  That allows up to 25 nations to be logged in
           in 30 seconds.
        */
        objXhttp.onreadystatechange = function() {
          if (objXhttp.readyState == 4) {
            if (objXhttp.status == 200) {
              strMessage = strMessage + "<br>" + strNation + ": OK";
              setTimeout(function(){ fLogin(intNation + 1) }, 1200);
            } else if (objXhttp.status == 403) {
              strMessage = strNation + ": Forbidden (incorrect password?)<br>" + strMessage;
              setTimeout(function(){ fLogin(intNation + 1) }, 1200);
            } else if (objXhttp.status == 404) {
              strMessage = strNation + ": Not Found (nation CTEd / misspelled / doesn't exist?)<br>" + strMessage;
              setTimeout(function(){ fLogin(intNation + 1) }, 1200);
            } else if (objXhttp.status == 429) {
              strMessage = strNation + ": Too Many Requests.  Processing stopped<br>" + strMessage;
            } else {
              strMessage = strNation + ": Error " + objXhttp.status + "<br>" + strMessage;
              setTimeout(function(){ fLogin(intNation + 1) }, 1200);
            }
            document.getElementById("result").innerHTML = strMessage;
          }
        };
 
        /* Tell the object which nation URL to log in to. 
           Replace any space characters in the nation name (" ") with underlines ("_")
           Tell it the password.
           Tell it your contact details.
           Send the request to NS.
        */
        objXhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?nation="+strNation.replace(" ", "_")+"&q=ping", true);
        objXhttp.setRequestHeader(strPasswordType, strPassword)
        objXhttp.setRequestHeader("User-Agent", strUserAgent)
        objXhttp.send();
      } else {
        strMessage = "Processing Complete<br>" + strMessage;
        document.getElementById("result").innerHTML = strMessage;
      }
    }
  </script>
</body>
</html>
Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart PuppetMaster v1.4

     Version | Date        | Name           | Comment
     1.0     | 04 Dec 2017 | Wolfram & Hart | Created
     1.2     | 16 Dec 2017 | Wolfram & Hart | Changes following Roavin's advice:
             |             |                | Put nation / password pairs into an array.
             |             |                | Included 'setTimeout()' in fGetXml function
             |             |                | to leave suitable gap between logins.
             |             |                | Other minor changes.
     1.4     | 12 Feb 2018 | Wolfram & Hart | NS query now returns the WA status, region, and number of unread issues, TGs etc
             |             |                | Output is now to a table instead of paragraph.
             |             |                | WA nation is listed first, followed by nations in the Rejected Realms

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html
-->

<head>
  <title>W&H PuppetMaster</title>
  <style>
    body {background-color: black;}
    div {
      text-align: center;
      color: white;
      font-family: times;
    }
    table,th,td {
      border : 1px solid white;
      border-collapse: collapse;
    }
    th,td {
      padding: 5px;
    }
  </style>
</head>

<body>
  <div>
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>PuppetMaster</h3>
    <br>
    <button type="button" onclick="fMain()">View Puppets</button>
    <p id="err"></p>
    <table id="msg" align=center></table>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>

    /* **********************CHANGE THIS TO USE YOUR OWN EMAIL ADDRESS********************** */
    /* NationStates require a 'UserAgent', so you can be contacted if something goes wrong   */
    var strUserAgent = "myname@email.com";
    /* ************************************************************************************* */

    /* ***************CHANGE THIS ARRAY TO INCLUDE YOUR NATIONS AND PASSWORDS*************** */
    var aobjNtns = [
      {nation:"Nation 1", password:"Password1"},
      {nation:"Nation 2", password:"Password2"},
      {nation:"Nation 3", password:"Password3"},
      {nation:"Last Nation", password:"LastPassword"}
    ];
    /* ************************************************************************************* */

    /* Tell NS the format of the password.  This code uses the unencrypted text password.
       (The one you type into the NationStates website).  NS allows you to use an encrypted
       password instead.  See this page for more details:
       https://www.nationstates.net/pages/api.html#nationapi-privateshards
    */
    var strPasswordType = "X-Password";

    /* Set variables to record the nations processed.  This will update the html. */
    var strErr;
    var strWA;
    var strTRR;
    var strMsg;
    var strHdr = "<tr><th>Nation</th><th>Region</th><th>Notices</th><th>Telegrams</th><th>RMB</th><th>Issues</th></tr>";
    var intNtn = 0;

    /* The fMain function resets the 'msg' table to being blank.
       It then tells the code to login the first nation in the list.
    */
    function fMain() {
      strErr = "";
      strWA = "";
      strTRR = "";
      strMsg = "";
      document.getElementById("err").innerHTML = strErr;
      document.getElementById("msg").innerHTML = strMsg;
      fGetXml();
    }

    /* fGetXml checks if there is another nation to be processed, then requests the XML from NS */
    function fGetXml() {
      if (intNtn < aobjNtns.length) {
        var strNtn = aobjNtns[intNtn].nation.replace(" ", "_");  // get the nation name & password from the list
        var strPassword = aobjNtns[intNtn].password;
        intNtn = intNtn + 1;
        var objXhttp = new XMLHttpRequest();                     // Create an object to send the details to NS
        objXhttp.onreadystatechange = function() {
          if (objXhttp.readyState == 2) {
            setTimeout(function(){ fGetXml() }, 1200);  // readyState == 2 means the request was received.  Set the timer to send the next request.
          } else if (objXhttp.readyState == 4) {
            if (objXhttp.status == 200) {
              fProcessXml(this);
            } else {
              if (objXhttp.status == 403) {
                strErr = strNtn + ": Forbidden (incorrect password?)<br>" + strErr;
              } else if (objXhttp.status == 404) {
                strErr = strNtn + ": Not Found (nation CTEd / misspelled / doesn't exist?)<br>" + strErr;
              } else if (objXhttp.status == 429) {
                strErr = strNtn + ": Too Many Requests.  Processing stopped<br>" + strErr;
              } else {
                strErr = strNtn + ": Error " + objXhttp.status + "<br>" + strErr;
              };
              document.getElementById("err").innerHTML = strErr;
            };
          };
        };
        objXhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?nation=" + strNtn + "&q=wa+name+region+unread", true);
        objXhttp.setRequestHeader(strPasswordType, strPassword);
        objXhttp.setRequestHeader("User-Agent", strUserAgent);
        objXhttp.send();
      };
    }

    /* fProcess reads in the XML data.  It puts any WA nation to the top of the output,
       followed by any nations in the Rejected Realms, followed by all other nations.
    */
    function fProcessXml(objXhttp) {
      var objXML = objXhttp.responseXML;
      var strNtn = objXML.getElementsByTagName("NAME")[0].childNodes[0].nodeValue;
      var strRgn = objXML.getElementsByTagName("REGION")[0].childNodes[0].nodeValue;
      var strNtc = objXML.getElementsByTagName("NOTICES")[0].childNodes[0].nodeValue;
      if (strNtc == "0") { strNtc = ""; };
      var strTGs = objXML.getElementsByTagName("TELEGRAMS")[0].childNodes[0].nodeValue;
      if (strTGs == "0") { strTGs = ""; };
      var strRMB = objXML.getElementsByTagName("RMB")[0].childNodes[0].nodeValue;
      if (strRMB == "0") { strRMB = ""; };
      var strIss = objXML.getElementsByTagName("ISSUES")[0].childNodes[0].nodeValue;
      if (strIss == "0") { strIss = ""; };
      var strTmp = strRgn + "</th><th>" + strNtc +
        "</th><th>" + strTGs + "</th><th>" + strRMB + "</th><th>" + strIss + "</th></tr>"
      if (objXML.getElementsByTagName("UNSTATUS")[0].childNodes[0].nodeValue != "Non-member") {
        strWA = strWA + "<tr style='color:red;'><th>WA Member: " + strNtn + "</th><th>" + strTmp;
      } else if (strRgn == "the Rejected Realms") {
        strTRR = strTRR + "<tr><th>" + strNtn + "</th><th style='color:red;'>" + strTmp;
      } else {
        strMsg = strMsg + "<tr><th>" + strNtn + "</th><th>" + strTmp;
      };
      document.getElementById("msg").innerHTML = strHdr + strWA + strTRR + strMsg;
    }

  </script>
</body>
</html>

Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart PuppetMaster v1.6

     Version | Date        | Name           | Comment
     1.0     | 04 Dec 2017 | Wolfram & Hart | Created
     1.2     | 16 Dec 2017 | Wolfram & Hart | Changes following Roavin's advice:
             |             |                | Put nation / password pairs into an array.
             |             |                | Included 'setTimeout()' in fGetXml function to leave suitable gap between logins.
             |             |                | Other minor changes.
     1.4     | 12 Feb 2018 | Wolfram & Hart | NS query now returns the WA status, region, and number of unread issues, TGs etc
             |             |                | Output is now to a table instead of paragraph.
             |             |                | WA nation is listed first, followed by nations in the Rejected Realms
     1.6     | 20 Aug 2018 | Wolfram & Hart | Added 'fSetNextRequest()' to improve efficiency of request times
             |             |                |

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html

     Feedback: If you use this script, please drop [nation]Wolfram & Hart[/nation] a telegram. 
     It would be interesting to know how widely this was used (if at all!)
     Any suggestions for improvements and enhancements are welcome.
-->

<head>
  <title>W&H PuppetMaster</title>
  <style>
    body {background-color: black;}
    div {
      text-align: center;
      color: white;
      font-family: times;
    }
    table,th,td {
      border : 1px solid white;
      border-collapse: collapse;
    }
    th,td {
      padding: 5px;
    }
  </style>
</head>

<body>
  <div>
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>PuppetMaster</h3>
    <br>
    <button type="button" onclick="fMain()">View Puppets</button>
    <p id="err"></p>
    <table id="msg" align=center></table>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>

    /* **********************CHANGE THIS TO USE YOUR OWN EMAIL ADDRESS********************** */
    /* NationStates require a 'UserAgent', so you can be contacted if something goes wrong   */
    var strUserAgent = "myname@email.com";
    /* ************************************************************************************* */

    /* ***************CHANGE THIS ARRAY TO INCLUDE YOUR NATIONS AND PASSWORDS*************** */
    var aobjNtns = [
      {nation:"Nation 1", password:"Password1"},
      {nation:"Nation 2", password:"Password2"},
      {nation:"Nation 3", password:"Password3"},
      {nation:"Last Nation", password:"LastPassword"}
    ];
    /* ************************************************************************************* */

    /* Tell NS the format of the password.  This code uses the unencrypted text password.
       (The one you type into the NationStates website).  NS allows you to use an encrypted
       password instead.  See this page for more details:
       https://www.nationstates.net/pages/api.html#nationapi-privateshards
    */
    var strPasswordType = "X-Password";

    /* Set variables to record the nations processed.  This will update the html. */
    var strErr;
    var strWA;
    var strTRR;
    var strMsg;
    var strHdr = "<tr><th>Nation</th><th>Region</th><th>Notices</th><th>Telegrams</th><th>RMB</th><th>Issues</th></tr>";
    var intNtn = 0;

    /* NS can receive 25 requests in 30 seconds.  Recording the times that NS receives the requests
       enables us to send the latest request exactly 30 seconds after NS received the 25th-ago request.
    */
    var adteTime = [];

    /* The fMain function:
       Initialises the adteTime array with values from 'now' to 24 seconds in the future.
       Resets the 'msg' table to being blank.
       Tells the code to login the first nation in the list.
    */
    function fMain() {
      var dteNow = new Date().getTime();
      var i;
      for (i = 0; i < 24; i++) {
        adteTime[i] = dteNow + (i + 1) * 1200;
      }
      strErr = "";
      strWA = "";
      strTRR = "";
      strMsg = "";
      document.getElementById("err").innerHTML = strErr;
      document.getElementById("msg").innerHTML = strMsg;
      fGetXml();
    }

    /* fGetXml checks if there is another nation to be processed, then requests the XML from NS */
    function fGetXml() {
      if (intNtn < aobjNtns.length) {
        var strNtn = aobjNtns[intNtn].nation.replace(" ", "_");  // get the nation name & password from the list
        var strPassword = aobjNtns[intNtn].password;
        intNtn = intNtn + 1;
        var objXhttp = new XMLHttpRequest();                     // Create an object to send the details to NS
        objXhttp.onreadystatechange = function() {
          if (objXhttp.readyState == 2) {
            fSetNextRequest();
          } else if (objXhttp.readyState == 4) {
            if (objXhttp.status == 200) {
              fProcessXml(this);
            } else {
              if (objXhttp.status == 403) {
                strErr = strNtn + ": Forbidden (incorrect password?)<br>" + strErr;
              } else if (objXhttp.status == 404) {
                strErr = strNtn + ": Not Found (nation CTEd / misspelled / doesn't exist?)<br>" + strErr;
              } else if (objXhttp.status == 429) {
                strErr = strNtn + ": Too Many Requests.  Processing stopped<br>" + strErr;
              } else {
                strErr = strNtn + ": Error " + objXhttp.status + "<br>" + strErr;
              };
              document.getElementById("err").innerHTML = strErr;
            };
          };
        };
        objXhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?nation=" + strNtn + "&q=wa+name+region+unread", true);
        objXhttp.setRequestHeader(strPasswordType, strPassword);
        objXhttp.setRequestHeader("User-Agent", strUserAgent);
        objXhttp.send();
      };
    }


    /* fSetNextRequest adds an entry to the end of adteTime, set for 30 seconds from now.
       It then looks at the entry recorded for the 25th-ago request, to set up when the next request should go.       
    */
    function fSetNextRequest() {
      var intDly;
      var dteNow = new Date().getTime();
      adteTime.push(dteNow + 30000);
      intDly = adteTime.shift() - dteNow;
      if (intDly < 0) { intDly = 1; };
      setTimeout(function(){ fGetXml() }, intDly);
    }

    /* fProcess reads in the XML data.  It puts any WA nation to the top of the output,
       followed by any nations in the Rejected Realms, followed by all other nations.
    */
    function fProcessXml(objXhttp) {
      var objXML = objXhttp.responseXML;
      var strNtn = objXML.getElementsByTagName("NAME")[0].childNodes[0].nodeValue;
      var strRgn = objXML.getElementsByTagName("REGION")[0].childNodes[0].nodeValue;
      var strNtc = objXML.getElementsByTagName("NOTICES")[0].childNodes[0].nodeValue;
      if (strNtc == "0") { strNtc = ""; };
      var strTGs = objXML.getElementsByTagName("TELEGRAMS")[0].childNodes[0].nodeValue;
      if (strTGs == "0") { strTGs = ""; };
      var strRMB = objXML.getElementsByTagName("RMB")[0].childNodes[0].nodeValue;
      if (strRMB == "0") { strRMB = ""; };
      var strIss = objXML.getElementsByTagName("ISSUES")[0].childNodes[0].nodeValue;
      if (strIss == "0") { strIss = ""; };
      var strTmp = strRgn + "</th><th>" + strNtc +
        "</th><th>" + strTGs + "</th><th>" + strRMB + "</th><th>" + strIss + "</th></tr>"
      if (objXML.getElementsByTagName("UNSTATUS")[0].childNodes[0].nodeValue != "Non-member") {
        strWA = strWA + "<tr style='color:red;'><th>WA Member: " + strNtn + "</th><th>" + strTmp;
      } else if (strRgn == "the Rejected Realms") {
        strTRR = strTRR + "<tr><th>" + strNtn + "</th><th style='color:red;'>" + strTmp;
      } else {
        strMsg = strMsg + "<tr><th>" + strNtn + "</th><th>" + strTmp;
      };
      document.getElementById("msg").innerHTML = strHdr + strWA + strTRR + strMsg;
    }

  </script>
</body>
</html>

PostPosted: Tue Dec 05, 2017 7:24 pm
by Queen Yuno
Wolfram and Hart wrote:Puppet Login Script

Edit: Seems my code is dodgy, and each nation counts as two hits to NS. We're allowed 50 hits per 30 seconds, or else we get blocked for 15 mins. Until I can figure out how to make a nation count as only one hit, this code should be used for a max of 25 nations.

What
Use this code to log in to a list of your puppets with the push of one button. Handy to stop them CTEing!

How
On your PC or laptop, open a text editor, like Notepad. Copy the code (below) into the editor.

Put your own email address in the code (place indicated by all the asterisks).

Put your own Nation names and passwords in the code (place indicated by all the asterisks).

REMEMBER: If you share your computer, then potentially someone else will have access to these passwords.

Save the file as “PuppetLogin.html”

Check the filename. If it saved as “PuppetLogin.html.txt” or similar, rename it to “PuppetLogin.html”.

When you open the file again, it should get opened in your browser by default. You should see a black page with “Wolfram & Hart” written across the top.

When you click the button, the code should send login requests to NS. NS then sends a response back. The code associated with the response gets displayed on your webpage.

Why
Yes, I know there's already code out there that you can download. But I don't like downloading things, and I like to know how things work. So I wrote code that needs no downloading, and is filled with explanatory comments.

HTML can be written in a simple text editor, and JavaScript can be embedded directly in HTML. Both will be recognised by your browser without the need for any other software or downloads. So they're what I used.

This is the first time I've properly tried to code HTML and JavaScript, so I've added comments at practically every line of code, explaining what it does. That also means users know exactly what they are getting.

Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart Puppet Login Script v1.0

     Version | Date        | Name           | Comment
     1.0     | 04 Dec 2017 | Wolfram & Hart | Created

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html 

     Don't use this code to log in more than 25 puppets in 30 seconds.
     (That would break NationStates API rules, as at the time of writing).
-->

<head>
  <!-- Set the title, visible in the browser tab -->
  <title>W&H Puppet Login</title>

  <!-- Set the colour scheme and format for the page -->
  <style>
    body {background-color: black;}
    div {
      text-align:center;
      color: white;
      font-family: times;
    }
  </style>
</head>

<body>
  <div>
    <!-- Display headers.  Use <span> tags to format the "W", "&", and "H" in "Wolfram & Hart". -->
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>Puppet Login</h3>

    <!-- Add a blank line -->
    <br>

    <!-- Add a button.  When clicked, this will run the JavaScript code -->
    <button type="button" onclick="fLoginAll()">Login</button>

    <!-- Add an empty paragraph, ready to display results -->
    <p id="result"></p>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>

    /* **********************CHANGE THIS TO USE YOUR OWN EMAIL ADDRESS********************** */
    /* NationStates require a 'UserAgent', so you can be contacted if something goes wrong */
    var strUserAgent = "myname@email.com"
    /* ************************************************************************************* */

    /* Tell NS the format of the password.  This code uses the unencrypted text password.
       (The one you type into the NationStates website).  NS allows you to use an encrypted
       password instead.  See this page for more details:
       https://www.nationstates.net/pages/api.html#nationapi-privateshards
    */
    var strPasswordType = "X-Password"

    /* Set a variable to record the nations processed.  This will update the html paragraph */
    var strMessage = "";

    /* ************CHANGE THIS TO INCLUDE UP TO 25 OF YOUR NATIONS AND PASSWORDS*********** */
    /* This function calls the fLogin function (below) for each nation / password pair.
       You can have up to 25 nations here without breaking NS API rules */
    function fLoginAll() {
      fLogin("Nation 1", "Password1")
      fLogin("Nation 2", "Password2")
      fLogin("Nation 3", "Password3")
      fLogin("Nation 4", "Password4")
    }
    /* ************************************************************************************* */

    /* This function uses AJAX to 'ping' (login) each nation, and records if the ping worked. */
    function fLogin(strNation, strPassword) {
      /* Create an object to send the login details to NS */
      var xhttp = new XMLHttpRequest();

      /* Set the object to update the html paragraph when it gets a response from NS */
      xhttp.onreadystatechange = function() {
        if (xhttp.readyState == 4) {
          strMessage = strMessage+"<br>" + strNation + ": " + xhttp.status;
          if (xhttp.status == 200) {
            strMessage = strMessage + ": OK";
          } else if (xhttp.status == 403) {
            strMessage = strMessage + ": Forbidden (incorrect password?)";
          } else if (xhttp.status == 404) {
            strMessage = strMessage + ": Not Found (nation doesn't exist?)";
          } else if (xhttp.status == 429) {
            strMessage = strMessage + ": Exceeding Rate Limit (max 25 nations every 30 seconds)";
          }
          document.getElementById("result").innerHTML = strMessage;
        }
      };

      /* Tell the object which nation URL to log in to. 
         Replace any space characters in the nation name (" ") with underlines ("_")
         Tell it the password.
         Tell it your contact details.
         Send the request to NS.
      */
      xhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?nation="+strNation.replace(" ", "_")+"&q=ping", true);
      xhttp.setRequestHeader(strPasswordType, strPassword)
      xhttp.setRequestHeader("User-Agent", strUserAgent)
      xhttp.send();
    }
  </script>
</body>
</html>


wow that's pretty cool
would it be legal if i put 1000 puppets in that script if i don't change anything else?

PostPosted: Wed Dec 06, 2017 5:50 am
by Wolfram and Hart
Thanks for your support!

We're allowed 50 'hits' on the server every 30 seconds. Each nation counts as two hits in my code (don't know why yet), so that's a max of 25 nations in 30 seconds. If you put in more than 25, you get blocked from doing more hits for 15 minutes.

It would be good to add a timer. The code could split the list into chunks of 25 nations, then send them out every 30 seconds. That would comply with site rules.

I'll work on that at some point. Though anyone else is welcome to post how they would do that!

PostPosted: Wed Dec 06, 2017 6:21 am
by Roavin
Wolfram and Hart wrote:We're allowed 50 'hits' on the server every 30 seconds. Each nation counts as two hits in my code (don't know why yet), so that's a max of 25 nations in 30 seconds.


That might be because Chrome is sending an additional OPTIONS request. Auralia had a similar problem. In most cases, it can be worked around by just halving the rate limit; unless you have that many, it won't make it unbearably slow (auto-logon scripts used to do one login every 10 seconds to stay compliant).

Wolfram and Hart wrote:It would be good to add a timer. The code could split the list into chunks of 25 nations, then send them out every 30 seconds. That would comply with site rules.

I'll work on that at some point. Though anyone else is welcome to post how they would do that!


(at risk of veering into Technical-territory)

[v] has stated that she prefers spaced out request rather than bunched up requests. You could have fLogin() place the nation/password tuples into a queue, then a function processNextNation() picks up the frontmost nation from the queue, does the thing, and then at the end of the onreadystatechange a setTimeout() call with an appropriate delay calls processNextNation() again. Note that if you detect HTTP 429, it includes a X-Retry-After header that you can parse to know when to try again (so your setTimeout() call would then use that time instead of the rate limit).

PostPosted: Wed Dec 06, 2017 12:15 pm
by Wolfram and Hart
Nice! Great advice. Much appreciated.

Wolfram and Hart Special Operations Update

PostPosted: Tue Dec 12, 2017 8:34 am
by Honeydewistania
Wolfram and Hart have been working on some Special Operations defending the defenceless and the occasional tag.
Regions Defended:
Sikh Empire with help from South Asians
Pacific Islands
Eurasian Socialist Union helping The Union of Democratic States
Giveran Empire
Regions Tagged:
Sontami

Special Operatives Present:
Port Honeydew
Wolfram and Hart
Attorneys Office

Excited? Are you thrilled just by looking at this posts? Do you want to be like us, super cool? Contact Wolfram and Hart.

PostPosted: Tue Dec 12, 2017 9:01 am
by The Noble Thatcherites
Honeydewistania wrote:
Wolfram and Hart have been working on some Special Operations defending the defenceless and the occasional tag.
Regions Defended:
Sikh Empire with help from South Asians
Pacific Islands
Eurasian Socialist Union helping The Union of Democratic States
Giveran Empire
Regions Tagged:
Sontami

Special Operatives Present:
Port Honeydew
Wolfram and Hart
Attorneys Office

Excited? Are you thrilled just by looking at this posts? Do you want to be like us, super cool? Contact Wolfram and Hart.
I didn’t realize that we where working together. That’s awesome. I look forward to working together more often.

PostPosted: Sun Dec 17, 2017 2:47 pm
by Wolfram and Hart
I've updated the Puppet Login code.
I took some of Roavin's advice, and spaced the login requests apart. I think the way I've done it should avoid the "429 - Too many logins" error.
And Queen Yuno, this version should be able to handle your 1000 puppets!

PostPosted: Sat Dec 30, 2017 6:11 pm
by Wolfram and Hart
Telegram Script

I've been playing around with code again, and came up with this script to telegram new nations. It's very basic. There are far better apps out there (and I'd strongly recommend you use them rather than this), so I'm sharing this mostly for novelty value.

Check out these threads if you're looking for a proper app.
viewtopic.php?f=15&t=432124
viewtopic.php?f=15&t=388960
viewtopic.php?f=15&t=350586

Edit: I've updated to version 1.2. This version should keep trying to TG new nations until your PC goes to sleep.

What

Use this code to send recruitment telegrams to 49 of the 50 most recently created nations (yeah, there's a bug!)
Use this code (v1.2) to send recruitment telegrams to new nations.

How

You need a Client Key, and the telegram ID and Secret Key for your recruitment telegram. I'm not going to explain how to get them here. If you really want to send out recruitment TGs, you'll easily find out for yourself, and you'll also likely want to use better software!

On your PC or laptop, open a text editor, like Notepad. Copy the code (below) into the editor.

Put your own Client Key, telegram ID and Secret Key in the code (place indicated by all the asterisks).

Save the file as “TelegraphMachine.html”

Check the filename. If it saved as “TelegraphMachine.html.txt” or similar, rename it to “TelegraphMachine.html”.

When you open the file again, it should get opened in your browser by default. You should see a black page with “Wolfram & Hart” written across the top.

When you click the button, the code asks NS for a list of the 50 most recently created nations.
It then attempts to send a telegram to the first nation on the list.
At this point, I always seem to get an "Error 0" message.
Three minutes later, the next telegram gets sent out.(This one should be OK)
Then three minutes later, the next, and so on until the list of 50 nations is finished.
The code then gets a fresh list of the 50 most recently created nations, and repeats.

Possible Improvements

Add text boxes so users can input Client Key etc on the screen.
Add a text box so users can supply their own list of nations.
Add a radio button to control telegram speed. The default would be 3 minutes (the fastest rate currently allowed for recruitment TGs). The alternative would be 30 seconds (permitted for non-recruitment TGs).
Check that the nation accepts recruitment telegrams before sending them one.
Create a loop, so that when all 50 new nations have been telegrammed, the next 50 are chosen. Without sending duplicate TGs to the same nations!


Feedback
If you use this script, please drop [nation]Wolfram & Hart[/nation] a telegram. It would be interesting to know how widely this was used (if at all!). Any suggestions for improvements and enhancements are welcome.
Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart Telegraph Machine\ Script v1.0

     Version | Date        | Name           | Comment
     1.0     | 23 Dec 2017 | Wolfram & Hart | Created

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html 

     Feedback: If you use this script, please drop [nation]Wolfram & Hart[/nation] a telegram. 
     It would be interesting to know how widely this was used (if at all!)
     Any suggestions for improvements and enhancements are welcome.
-->

<head>
  <!-- Set the title, visible in the browser tab -->
  <title>W&H Telegraph Machine</title>

  <!-- Set the colour scheme and format for the page -->
  <style>
    body {background-color: black;}
    div {
      text-align:center;
      color: white;
      font-family: times;
    }
  </style>
</head>

<body>
  <div>
    <!-- Display headers.  Use <span> tags to format the "W", "&", and "H" in "Wolfram & Hart". -->
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>Telegraph Machine</h3>

    <!-- Add a blank line -->
    <br>

    <!-- Add a button.  When clicked, this will run the JavaScript function, "fMain" -->
    <button type="button" onclick="fMain()">Telegram New Nations</button>

    <!-- Add an empty paragraph, ready to display results -->
    <p id="result"></p>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>

    /* ***********INSERT YOUR OWN CLIENT KEY, TG ID, AND SECRET KEY IN THIS SECTION******** */
    /* Need client key, telegram ID, and the secret key. 
       See https://www.nationstates.net/pages/api.html#telegrams for more info.
    */
    var strClientKey = "abcd1234"
    var strTgId = "12345678"
    var strSecretKey = "12345678abcd"
    /* ************************************************************************************* */

    /* Set TG speed (recruitment = 180s, non recruitment = 30s) */
    var intSpeed = 180000

    /* Set an array variable to store the list of nations */
    var astrNations = [];

    /* Set a variable to record the nations processed.  This will update the html paragraph. */
    var strMessage = "";

    /* The fMain function resets the 'result' paragraph to being blank.
       It then gets a list of new nations from NS (should be 50 most recently created)
       It waits until after the list of nations is received before trying to send a TG
    */
    function fMain() {
      strMessage = "";
      document.getElementById("result").innerHTML = "";
      fGetNewNations();
    }

    /* The fGetNewNations function requests the list of new nations from NS
       When the list is in, it calls the fTelegram function, to send the first TG.
    */
    function fGetNewNations() {
      /* Create an object to send the details to NS */
      var objXhttp = new XMLHttpRequest();
      objXhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 200) {
          astrNations = this.responseText.split(",");
          fTelegram(0);
        }
      };
      objXhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?q=newnations", true);
      objXhttp.send();
    }

    /* the fTelegram function sends out the TG, logs a response, then sets a timer to send the next one */
    function fTelegram(intNation) {

      /* If there are still nations to be processed, then process them */
      if (intNation < astrNations.length) {

        /* get the nation name from the list */
        var strNation = astrNations[intNation];

        /* Create an object to send the details to NS */
        var objXhttp = new XMLHttpRequest();

        /* Set the object to update the html paragraph when it gets a response from NS
           When a response is received, update the paragraph.  Then wait before telegramming the next nation.
        */
        objXhttp.onreadystatechange = function() {
          if (objXhttp.readyState == 4) {
            if (objXhttp.status == 200) {
              strMessage = strMessage + "<br>" + strNation + ": OK";
              setTimeout(function(){ fTelegram(intNation + 1) }, intSpeed);
            } else {
              strMessage = strNation + ": Error " + objXhttp.status + ": " + objXhttp.responseText + "<br>" + strMessage;
              setTimeout(function(){ fTelegram(intNation + 1) }, intSpeed);
            }
            document.getElementById("result").innerHTML = strMessage;
          }
        };

        objXhttp.open("GET",
          "https://www.nationstates.net/cgi-bin/api.cgi?a=sendTG&client=" + strClientKey
          + "&tgid=" + strTgId + "&key=" + strSecretKey + "&to=" + strNation.replace(" ", "_"), true);
        objXhttp.send();
      } else {
        strMessage = "Processing Complete<br>" + strMessage;
      }
    }
  </script>
</body>
</html>


Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart Telegraph Machine Script v1.2

     Version | Date        | Name           | Comment
     1.0     | 23 Dec 2017 | Wolfram & Hart | Created
     1.2     | 03 Feb 2018 | Wolfram & Hart | Restructured to use Callback function with AJAX request.
             |             |                | Loop added to keep looking for new nations.
             |             |                | Bug fix for 1st and last nation name processing.

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html 
-->

<head>
  <title>W&H Telegraph Machine</title>
  <style>
    body {background-color: black;}
    div {
      text-align:center;
      color: white;
      font-family: times;
    }
  </style>
</head>

<body>
  <div>
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>Telegraph Machine</h3>
    <br>
    <button type="button" onclick="fMain()">Telegram New Nations</button>
    <p id="err"></p>
    <p id="msg"></p>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>

    /* Get a list of the 50 most recently created nations.
       For each nation:
       If the nation accepts recruitment TGs, send one.
       Move on to the next nation.
       When all 50 nations are done, start again.
    */

    /* *******INSERT YOUR OWN CLIENT KEY, TG ID, SECRET KEY AND REGION IN THIS SECTION****** */
    var strClientKey = "abcd1234";
    var strTgId = "12345678";
    var strSecretKey = "123456abcdef";
    var strRegion = "MyRegion";
    /* ************************************************************************************* */

    var intSpdTG = 180000;  // Recruitment TGs can be sent every 180 seconds (note non-recruitment TGs can be sent every 30 seconds)
    var intSpdAPI = 1200;   // Regular API calls to NS can be sent every 1.2 seconds (actually 50 "hits" in 30 seconds, but this seems to work)
    var astrNtns = [];      // An array variable to store the list of new nations
    var intNtn = 0;         // Keep a note of which nation is being processed
    var strErr = "";        // Keep a record of any HTTP errors
    var strMsg = "";        // Keep a record of nations successfully processed

    /* The fMain function is called when the button is pressed.
       Reset displayed paragraphs to be blank, then request a list of new NS nations
    */
    function fMain() {
      strRegion = strRegion.replace(" ", "_");
      strErr = "";
      strMsg = "";
      document.getElementById("err").innerHTML = strErr;
      document.getElementById("msg").innerHTML = strMsg;
      fGetNewNations();
    }

    /* The fGetNewNations function requests the list of new nations from NS */
    function fGetNewNations() {
      fGetXml("https://www.nationstates.net/cgi-bin/api.cgi?q=newnations", fProcessNewNations);
    }

    /* The fProcessNewNations function parses the list of new nations into an array
       It then tries to recruit the first nation (0) on the list
    */
    function fProcessNewNations(objXml) {
      if (objXml.status == 200) {
        astrNtns = objXml.responseXML.getElementsByTagName("NEWNATIONS")[0].childNodes[0].nodeValue.split(",");
        intNtn = 0;
        setTimeout(function(){ fGetCanRecruit() }, intSpdAPI);
      } else {
        strErr = "fGetNewNations Error " + objXml.status + ": " + objXml.responseText + "<br>" + strErr;
        document.getElementById("err").innerHTML = strErr;
      }
    }

    /* The fGetCanRecruit function checks if the nation accepts recruitment TGs
       Sending the optional "from=(region_name)" parameter checks that you didn't send them a TG too recently.
    */
    function fGetCanRecruit() {
      /* First check that there are still nations to process */
      if (intNtn >= astrNtns.length) {
        fGetNewNations();
      } else {
        /* get the nation name from the list */
        var strNtn = astrNtns[intNtn].replace(" ", "_");
        var strUrl = "https://www.nationstates.net/cgi-bin/api.cgi?nation=" + strNtn + "&q=tgcanrecruit&from=" + strRegion
        fGetXml(strUrl, fProcessCanRecruit);
      }
    }
       
    /* The fProcessCanRecruit function:
       If the response is "1", (Nation accepts recruitment TGs), send a TG
       Else, try the next nation
    */
    function fProcessCanRecruit(objXml) {
      if (objXml.status == 200) {
        var strMrkr = objXml.responseXML.getElementsByTagName("TGCANRECRUIT")[0].childNodes[0].nodeValue;
        if (strMrkr == "1") {
          setTimeout(function(){ fTelegram() }, intSpdAPI);
        } else {
          strMsg = intNtn + ": " + astrNtns[intNtn] + ": TG Blocked" + "<br>" + strMsg;
          document.getElementById("msg").innerHTML = strMsg;
          intNtn = intNtn + 1;
          setTimeout(function(){ fGetCanRecruit() }, intSpdAPI);
        }
      } else {
        strErr = "fGetCanRecruit Error " + objXml.status + ": " + objXml.responseText + "<br>" + strErr;
        document.getElementById("err").innerHTML = strErr;
        intNtn = intNtn + 1;
        setTimeout(function(){ fGetCanRecruit() }, intSpdAPI);
      }
    }

    /* The fTelegram function sends out the TG */
    function fTelegram() {
      var strNtn = astrNtns[intNtn].replace(" ", "_");
      var strUrl = "https://www.nationstates.net/cgi-bin/api.cgi?a=sendTG&client="
        + strClientKey + "&tgid=" + strTgId + "&key=" + strSecretKey + "&to=" + strNtn;
      fGetXml(strUrl, fProcessTelegram);
    }

    /* Log the response, then check the next nation */
    function fProcessTelegram(objXml) {
      if (objXml.status == 200) {
        strMsg = intNtn + ": " + astrNtns[intNtn] + ": TG Sent" + "<br>" + strMsg;
        document.getElementById("msg").innerHTML = strMsg;
      } else {
        strErr = "fTelegram Error " + objXml.status + ": " + objXml.responseText + "<br>" + strErr;
        document.getElementById("err").innerHTML = strErr;
      }
      intNtn = intNtn + 1;
      setTimeout(function(){ fGetCanRecruit() }, intSpdTG - intSpdAPI);
    }

    /* fGetXml needs to know the URL to get data from, and the function to run when the data is retrieved. */
    function fGetXml(strUrl, objFn) {
      var objXhttp = new XMLHttpRequest();        // Create an object to send the details to NS
      objXhttp.onreadystatechange = function() {
        if (this.readyState == 4) {
            objFn(this);
        }
      };
      objXhttp.open("GET", strUrl, true);
      objXhttp.send();
    }

  </script>
</body>
</html>

PostPosted: Tue Feb 13, 2018 3:12 pm
by Wolfram and Hart
I've updated the Puppet Login Script. Now on version 1.4. This one displays the region your puppet is in, and a count of any unread notices, TGs, RMB posts, and unanswered issues. It also puts your WA nation at the top of the screen (handy if you forgot where you left your WA!), followed by nations in the Rejected Realms (handy to see if your puppets got booted out of a region).

PostPosted: Thu Oct 25, 2018 2:53 pm
by Wolfram and Hart
Sleeping Beauty

What
This code logs in your puppets, and answers their issues according to pre-defined rules.

When setting up the code, the user gives it a target completion time. This is initially set to 60 minutes, though is easily changed. The code will try to answer issues for all your puppets within this time frame. It may finish sooner. Depending on the number of puppets, it may take longer. Your PC must be switched on the entire time the code is running, else some puppets may not get processed.

The code randomly chooses a time to log in each puppet, subject to the target completion time you give.

The code logs into a puppet, records which region the puppet is in, how many unread notifications, telegrams, RMB posts, and issues there are. If the nation is your WA nation, it will be displayed in red. The code then starts answering issues according to pre-defined rules. The code tries to leave gaps of 20 to 80 seconds between answering issues. The purpose of this delay is to try and make it look like a real player is making considered decisions.

If your target completion time is short, then a keen observer may notice that your puppets log in and answer issues at similar times. If your target completion time is longer, then it is harder for an observer to associate your puppets with each other.

The code allows for puppets to be processed concurrently - so Puppet1 can answer issues inbetween Puppet2's answers to their own issues. This behaviour aims to make it look more like the puppets are controlled by different players.

At the end of processing, the code should display a pop-up stating "Done".

The available pre-defined rules are:
1st: The code will always select the first issue answer that is available to the puppet.
2nd: The code will always select the second issue answer that is available to the puppet.
2nd last: The code will always select the second last issue answer that is available to the puppet.
last: The code will always select the last issue answer that is available to the puppet.
random: The code will always select a random issue answer that is available to the puppet.
If you give the code an unrecognised value for the rule (eg you tell the code to use "first" instead of "1st"), the code will not answer issues for that puppet.

These rules will not allow your puppets to follow a consistent political ideology, or to maximise a particular census score. To do so would require rules that can identify which issue answer was appropriate for your objective, for each of the 1000+ issues.


How
On your PC or laptop, open a text editor, like Notepad. Copy the code (below) into the editor.

Put your own email address in the code (place indicated by all the asterisks).

Put your own Nation names, passwords, and issue-answering styles (pre-defined rules) in the code (place indicated by all the asterisks).

Set your own target completion time (in minutes) in the code (place indicated by all the asterisks).

REMEMBER: If you share your computer, then potentially someone else will have access to your passwords.

Save the file as “SleepingBeauty.html”

Check the filename. If it saved as “SleepingBeauty.html.txt” or similar, rename it to “SleepingBeauty.html”.

When you open the file again, it should get opened in your browser by default. You should see a black page with “Wolfram & Hart” written across the top.

When you click the button, the code should send login requests to NS. NS then sends a response back, which will be displayed on your webpage.


Why
If you don't know why, this code probably isn't for you!


Feedback
If you use this, the Telegram script, or the PuppetMaster Login script, please drop me a telegram. It would be interesting to know how widely these were used.
Any suggestions for improvements and enhancements are welcome.

Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart Sleeping Beauty v1.0

     Version | Date        | Name           | Comment
     1.0     | 25 Oct 2018 | Wolfram & Hart | Created

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html

     Feedback: If you use this script, please drop [nation]Wolfram & Hart[/nation] a telegram. 
     It would be interesting to know how widely this was used (if at all!)
     Any suggestions for improvements and enhancements are welcome.
-->

<head>
  <title>W&H Sleeping Beauty</title>
  <style>
    body {background-color: black;}
    div {
      text-align: center;
      color: white;
      font-family: times;
    }
    table,th,td {
      border : 1px solid white;
      border-collapse: collapse;
    }
    th,td {
      padding: 5px;
    }
  </style>
</head>

<body>
  <div>
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>Sleeping Beauty</h3>
    <br>
    <button type="button" onclick="fMain()">Process Puppets</button>
    <p><i>(Click table header to sort data)</i></p>
    <p id="log"></p>
    <p id="err"></p>
    <table id="msg" align=center><tr>
      <th onclick="fSortNtns('ntn')">Nation</th>
      <th onclick="fSortNtns('rgn')">Region</th>
      <th onclick="fSortNtns('ntc')">Notices</th>
      <th onclick="fSortNtns('tgs')">Telegrams</th>
      <th onclick="fSortNtns('rmb')">RMB</th>
      <th onclick="fSortNtns('iss')">Issues</th>
    </tr></table>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>


    /* **********************CHANGE THIS TO USE YOUR OWN EMAIL ADDRESS********************** */
    /* NationStates require a 'UserAgent', so you can be contacted if something goes wrong   */
    var strUserAgent = "myname@email.com";
    /* ************************************************************************************* */


    /* ***********CHANGE THIS ARRAY TO INCLUDE YOUR NATIONS, PASSWORDS, AND STYLES********** */
    var aobjNtns = [
      {nation:"Nation 1", password:"Password1", style:"1st"},
      {nation:"Nation 2", password:"Password2", style:"2nd"},
      {nation:"Nation 3", password:"Password3", style:"2nd last"},
      {nation:"Nation 4", password:"Password4", style:"last"},
      {nation:"Last Nation", password:"LastPassword", style:"random"}
    ];
    /*
    var aobjNtns = [
    ];
    */
    /* ************************************************************************************* */


    /* *****************SET THE TIME (IN MINUTES) TO TRY AND FINISH BY********************** */
    var intEnd = 60;
    /* ************************************************************************************* */


    var strPasswordType = "X-Password";   // Tell NS the format of the above passwords.  This info is needed by NS
    var dteNow;            // Used at various points to record the current time
    var strNtnLast = "";      // Record the last nation processed to determine whether Pin or Password is used for next login.

    var strErr;            // Set variables to record the nations processed.  These are used to update the html.
    var strMsg;
    var strHdr = document.getElementById("msg").innerHTML;
    var strSort = "";

    /* The fMain function:
       Convert the target end time, intEnd, to millisecconds, and subtract estimated required processing times.
       After this convertion, intEnd represents the latest time that the code should start handling a nation.
       Reset the 'msg' table to being blank.
       Put the list of nations into a random order, then adds them to the request queue.
       Tell the code to login the first nation in the queue.
    */
    function fMain() {
      dteNow = new Date().getTime();               // Note the time.  Used when adding nations to the request queue
      intEnd = (intEnd * 60000) - (aobjNtns.length * 1200) - 400000;   // Convert target end time to ms and adjust
      if (intEnd < 0) { intEnd = 1000; };            // Make sure the new end time is at least one second long!
      strErr = "";                     // Reset the message strings and blank out the html
      strMsg = strHdr;
      document.getElementById("err").innerHTML = strErr;
      document.getElementById("msg").innerHTML = strMsg;
      aobjNtns = aobjNtns.map(fRemapNtns);            // Add some extra detail to the nation records
      aobjRqst = aobjNtns.map(fAddNtnsToRqstQueue);         // Add the nations to the request queue
      aobjRqst.sort(fSortWhen);                  // Sort requests into the order in which they'll be made
      fGetXml();                     // Request the first item in the request queue
    }

    /* fRemapNtns expands each Nation object to hold the data that will be returned from NS */
    function fRemapNtns(value, index, array) {
      return {
        name: value.nation,
        ntn: value.nation.toLowerCase().replace(/ /g, "_"),
        styl: value.style.toLowerCase(),
        pwrdtyp: strPasswordType,
        pwrd: value.password,
        pin: "",
        wa: "",
        rgn: "?",
        ntc: -1,
        tgs: -1,
        rmb: -1,
        iss: -1
      };
    }

    /* fAddNtnsToRqstQueue takes the list of nations and passwords, and adds them to a queue of requests to be sent to NS
       Set a random time, based on intEnd, to start handling the nation.
    */
    function fAddNtnsToRqstQueue(value, index, array) {
      return {
        ntn: value.ntn,
        rqst: "&q=wa+name+region+unread+issues",
        pwrdtyp: value.pwrdtyp,
        pwrd: value.pwrd,
        when: dteNow + intEnd * Math.random()
      };
    }

    /* function to sort array by scheduled request time */
    function fSortWhen(a,b) {
      return a.when - b.when;
    }

    /* fGetXml takes the first request to be processed, then requests the XML from NS
       When the result is back, call fProcessXml.
    */
    function fGetXml() {
      if (aobjRqst.length > 0) {
        var objRqst = aobjRqst.shift();            // Take the first item in the queue
        var objXhttp = new XMLHttpRequest();                    // Create an object to send the details to NS
        objXhttp.onreadystatechange = function() {
          if (objXhttp.readyState == 4) {
            if (objXhttp.status == 200) {
              fProcessXml(this);            // When a response is received from NS, process it
            } else {
              if (objXhttp.status == 403) {
                strErr = objRqst.ntn + ": Forbidden (incorrect password?)<br>" + strErr;
              } else if (objXhttp.status == 404) {
                strErr = objRqst.ntn + ": Not Found (nation CTEd / misspelled / doesn't exist?)<br>" + strErr;
              } else if (objXhttp.status == 429) {
                strErr = objRqst.ntn + ": Too Many Requests.  Processing stopped<br>" + strErr;
              } else {
                strErr = objRqst.ntn + ": Error " + objXhttp.status + "<br>" + strErr;
              };
              document.getElementById("err").innerHTML = strErr;
            };
          };
        };
        // Set the details of the request:
        objXhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?nation=" + objRqst.ntn + objRqst.rqst, true);
        if (objRqst.ntn == strNtnLast) {
          var i = 0;
          while (aobjNtns[i].ntn != strNtnLast) { i++; };         // Find the matching nation in aobjNtns
          objXhttp.setRequestHeader("X-Pin", aobjNtns[i].pin);         // If this request is for the same nation as the last,
          objXhttp.setRequestHeader(objRqst.pwrdtyp, objRqst.pwrd);      // use the Pin.  Send password as well, just in case
        } else {
          objXhttp.setRequestHeader(objRqst.pwrdtyp, objRqst.pwrd);      // Else set to use the password, and
          strNtnLast = objRqst.ntn;                  // record that this nation is now the latest nation
        };
        objXhttp.setRequestHeader("User-Agent", strUserAgent);         // Set to send the User-Agent too
        objXhttp.send();                     // Send the request to NS
      };
    }

    /* fProcess:
       Reads in the XML data.
       Updates the nation with the new Pin
       Works out when to call the routine that sends the next request
    */
    function fProcessXml(objXhttp) {
      dteNow = new Date().getTime();
      var objXML = objXhttp.responseXML;               // Read in the body of the returned data
      var strNtn = objXML.getElementsByTagName("NATION")[0].getAttribute("id");   // Read the nation ID
      var i = 0;
      while (aobjNtns[i].ntn != strNtn) { i++; };            // Find the matching nation in aobjNtns
      var strPin = objXhttp.getResponseHeader("X-Pin");            // Read in the pin from returned data header
      if (strPin != null) { aobjNtns[i].pin = strPin; };         // Assign the pin to the nation
      if (objXML.getElementsByTagName("UNREAD").length > 0) {
        fProcessFirst(objXhttp, strNtn, i);         // If this was the first request for the nation, process accordingly
      } else {
        fProcessIssue(objXML, i);            // If this was the result of answering an issue, process accordingly
      };
      if (aobjRqst.length > 0) {
        var intWhen = aobjRqst[0].when - dteNow;      // If there are more requests in the queue, work out when to send the next
        if (intWhen < 1200) { intWhen = 1200; };
        setTimeout(function(){ fGetXml() }, intWhen);
      } else {
        window.alert("Done");               // Else notify the user that the job is complete
      };
    }

    /* fProcess reads in the XML data.
    */
    function fProcessFirst(objXhttp, strNtn, i) {
      var dteWhen = dteNow;
      var strTmp
      var objXML = objXhttp.responseXML;                     // Read in the body of the returned data
      aobjNtns[i].pwrdtyp = "X-Autologin";
      var strPwrd = objXhttp.getResponseHeader("X-Autologin");               // Read in the password from returned data header
      aobjNtns[i].pwrd = strPwrd;                        // Update the nation with the returned data
      aobjNtns[i].wa = objXML.getElementsByTagName("UNSTATUS")[0].childNodes[0].nodeValue;
      aobjNtns[i].rgn = objXML.getElementsByTagName("REGION")[0].childNodes[0].nodeValue;
      aobjNtns[i].ntc = objXML.getElementsByTagName("NOTICES")[0].childNodes[0].nodeValue;
      aobjNtns[i].tgs = objXML.getElementsByTagName("TELEGRAMS")[0].childNodes[0].nodeValue;
      aobjNtns[i].rmb = objXML.getElementsByTagName("RMB")[0].childNodes[0].nodeValue;
      aobjNtns[i].iss = objXML.getElementsByTagName("ISSUES")[0].childNodes[0].nodeValue;
      var aobjIss = objXML.getElementsByTagName("ISSUE");               // Create an array of the issues data
      var strOpt = "ignore";
      for (var j = 0; j < aobjIss.length; j++) {
        var aobjOpts = aobjIss[j].getElementsByTagName('OPTION');            // For each issue, find the options & choose response
        switch (aobjNtns[i].styl) {
          case "random":
            strOpt = aobjOpts[Math.floor(Math.random() * aobjOpts.length)].getAttribute('id');   // Choose a random answer
            break;
          case "1st":
            strOpt = aobjOpts[0].getAttribute('id');                  // Choose the first available answer
            break;
          case "2nd":
            strOpt = aobjOpts[1].getAttribute('id');                  // Choose the second available answer
            break;
          case "2nd last":
            strOpt = aobjOpts[aobjOpts.length - 2].getAttribute('id');            // Choose the second last answer
            break;
          case "last":
            strOpt = aobjOpts[aobjOpts.length - 1].getAttribute('id');            // Choose the last answer
            break;
        };                                 // Else the default treatment is to ignore issues
        if (strOpt != "ignore") {
          dteWhen = dteWhen + 20000 + 60000 * Math.random();               // Set the gap between issue answers to 20 to 80 seconds
          var objRqst = {
            ntn: strNtn,
            rqst: "&c=issue&issue=" + aobjIss[j].getAttribute("id") + "&option=" + strOpt,
            pwrdtyp: "X-Autologin",
            pwrd: strPwrd,
            when: dteWhen
          };
          aobjRqst.push(objRqst);                        // Add the nation, issue, and answer to the request queue
        };   
      };
      aobjRqst.sort(fSortWhen);                           // Make sure the request queue is in the right order     
      fSortNtns(strSort);                           // Update the visible HTML table
    }   

    /* fProcessIssue:
       Reduces the nation's issue count by one, if an issue was successfully answered
    */
    function fProcessIssue(objXML, i) {
      if (objXML.getElementsByTagName("OK").length > 0) {
        aobjNtns[i].iss = aobjNtns[i].iss - 1;            // If the issue was successfully answered, reduce the number of outstanding issues
        fSortNtns(strSort);                  // Update the visible HTML table
      };
    }

    function fSortNtns(strBy) {
      strSort = strBy; 
      switch (strBy) {
        case "ntn":
          aobjNtns.sort(fSortNtn);
          break;
        case "rgn":
          aobjNtns.sort(fSortRgn);
          break;
        case "ntc":
          aobjNtns.sort(fSortNtc);
          break;
        case "tgs":
          aobjNtns.sort(fSortTgs);
          break;
        case "rmb":
          aobjNtns.sort(fSortRmb);
          break;
        case "iss":
          aobjNtns.sort(fSortIss);
          break;
      };
      fUpdateTable();
    }

    /* function to sort array by nation */
    function fSortNtn(a,b) {
      var x = a.ntn.toLowerCase();
      var y = b.ntn.toLowerCase();
      if (x < y) { return -1; };
      if (x > y) { return 1; };
      return 0;
    }

    /* function to sort array by region */
    function fSortRgn(a,b) {
      var x = a.rgn.toLowerCase();
      var y = b.rgn.toLowerCase();
      if (x < y) { return -1; };
      if (x > y) { return 1; };
      return 0;
    }

    /* function to sort array by nation sort order */
    function fSortNtc(a,b) {
      return b.ntc - a.ntc;
    }

    /* function to sort array by nation sort order */
    function fSortTgs(a,b) {
      return b.tgs - a.tgs;
    }

    /* function to sort array by nation sort order */
    function fSortRmb(a,b) {
      return b.rmb - a.rmb;
    }

    /* function to sort array by nation sort order */
    function fSortIss(a,b) {
      return b.iss - a.iss;
    }

    /*
    */
    function fUpdateTable() {
      var i;
      var strMsg = strHdr;                     // Set the table header
      var strNtc;
      var strTgs;
      var strRmb;
      var strIss;
      for (i = 0; i < aobjNtns.length; i++) {
        if (aobjNtns[i].wa == "WA Member") {
          strMsg = strMsg + "<tr style='color:red;'>";            // Set to display any WA nation in red
        } else {
          strMsg = strMsg + "<tr>";
        };
        strNtc = aobjNtns[i].ntc;                  // If 'unread' counts are unknown, display them as "?"
        if (strNtc < 0) { strNtc = "?" };
        strTgs= aobjNtns[i].tgs;
        if (strTgs < 0) { strTgs = "?" };
        strRmb = aobjNtns[i].rmb;
        if (strRmb < 0) { strRmb = "?" };
        strIss = aobjNtns[i].iss;
        if (strIss < 0) { strIss = "?" };
        strMsg = strMsg + "<td>"
          + aobjNtns[i].name + "</td><td>" + aobjNtns[i].rgn + "</td><td>"
          + strNtc + "</td><td>" + strTgs + "</td><td>"
          + strRmb + "</td><td>" + strIss + "</td></tr>";         // Add details of each nation
      };
      document.getElementById("msg").innerHTML = strMsg;         // Output the complete data to the HTML table
    }
  </script>
</body>
</html>


Edit: Version 1.2. Returns SPDR for each nation. Code skips over unrecognised / CTEd nations instead of stopping.

Code: Select all
<!DOCTYPE html>

<!-- Wolfram & Hart Sleeping Beauty v1.2

     Version | Date        | Name           | Comment
     1.0     | 25 Oct 2018 | Wolfram & Hart | Created
     1.2     | 19 Nov 2018 | Wolfram & Hart | Included SPDR. Continues processing if nation or password invalid.

     This code is not warranted in any way.  Use at your own risk.

     Use and modify this code as suits.
     Make sure you comply with the NationStates API rules.
     See:  https://www.nationstates.net/pages/api.html
-->

<head>
  <title>W&H Sleeping Beauty</title>
  <style>
    body {background-color: black;}
    div {
      text-align: center;
      color: white;
      font-family: times;
    }
    table,th,td {
      border : 1px solid white;
      border-collapse: collapse;
    }
    th,td {
      padding: 5px;
    }
  </style>
</head>

<body>
  <div>
    <h1><span style="font-size:120%;">W</span>OLFRAM <span style="color:red; font-size:120%;">&</span> <span style="font-size:120%;">H</span>ART</h1>
    <h3>Sleeping Beauty</h3>
    <br>
    <button type="button" onclick="fMain()">Process Puppets</button>
    <p><i>(Click table header to sort data)</i></p>
    <p id="log"></p>
    <p id="err"></p>
    <table id="msg" align=center><tr>
      <th onclick="fSortNtns('ntn')">Nation</th>
      <th onclick="fSortNtns('rgn')">Region</th>
      <th onclick="fSortNtns('ntc')">Notices</th>
      <th onclick="fSortNtns('tgs')">Telegrams</th>
      <th onclick="fSortNtns('rmb')">RMB</th>
      <th onclick="fSortNtns('iss')">Issues</th>
      <th onclick="fSortNtns('spdr')">Influence</th>
    </tr></table>
  </div>

  <!-- This is the JavaScript that will be run when the button is pressed -->
  <script>


    /* **********************CHANGE THIS TO USE YOUR OWN EMAIL ADDRESS********************** */
    /* NationStates require a 'UserAgent', so you can be contacted if something goes wrong   */
    var strUserAgent = "myname@email.com";
    /* ************************************************************************************* */


    /* ***********CHANGE THIS ARRAY TO INCLUDE YOUR NATIONS, PASSWORDS, AND STYLES********** */
    var aobjNtns = [
      {nation:"Nation 1", password:"Password1", style:"1st"},
      {nation:"Nation 2", password:"Password2", style:"2nd"},
      {nation:"Nation 3", password:"Password3", style:"2nd last"},
      {nation:"Nation 4", password:"Password4", style:"last"},
      {nation:"Last Nation", password:"LastPassword", style:"random"}
    ];
    /* ************************************************************************************* */


    /* *****************SET THE TIME (IN MINUTES) TO TRY AND FINISH BY********************** */
    var intEnd = 60;
    /* ************************************************************************************* */


    var strPasswordType = "X-Password";   // Tell NS the format of the above passwords.  This info is needed by NS
    var dteNow;            // Used at various points to record the current time
    var strNtnLast = "";      // Record the last nation processed to determine whether Pin or Password is used for next login.

    var strErr;            // Set variables to record the nations processed.  These are used to update the html.
    var strMsg;
    var strHdr = document.getElementById("msg").innerHTML;
    var strSort = "";

    /* The fMain function:
       Convert the target end time, intEnd, to millisecconds, and subtract estimated required processing times.
       After this convertion, intEnd represents the latest time that the code should start handling a nation.
       Reset the 'msg' table to being blank.
       Put the list of nations into a random order, then adds them to the request queue.
       Tell the code to login the first nation in the queue.
    */
    function fMain() {
      dteNow = new Date().getTime();               // Note the time.  Used when adding nations to the request queue
      intEnd = (intEnd * 60000) - (aobjNtns.length * 1200) - 400000;   // Convert target end time to ms and adjust
      if (intEnd < 0) { intEnd = 1000; };            // Make sure the new end time is at least one second long!
      strErr = "";                     // Reset the message strings and blank out the html
      strMsg = strHdr;
      document.getElementById("err").innerHTML = strErr;
      document.getElementById("msg").innerHTML = strMsg;
      aobjNtns = aobjNtns.map(fRemapNtns);            // Add some extra detail to the nation records
      aobjRqst = aobjNtns.map(fAddNtnsToRqstQueue);         // Add the nations to the request queue
      aobjRqst.sort(fSortWhen);                  // Sort requests into the order in which they'll be made
      fGetXml();                     // Request the first item in the request queue
    }

    /* fRemapNtns expands each Nation object to hold the data that will be returned from NS */
    function fRemapNtns(value, index, array) {
      return {
        name: value.nation,
        ntn: value.nation.toLowerCase().replace(/ /g, "_"),
        styl: value.style.toLowerCase(),
        pwrdtyp: strPasswordType,
        pwrd: value.password,
        pin: "",
        wa: "",
        rgn: "?",
        ntc: -1,
        tgs: -1,
        rmb: -1,
        iss: -1,
        spdr: -1
      };
    }

    /* fAddNtnsToRqstQueue takes the list of nations and passwords, and adds them to a queue of requests to be sent to NS
       Set a random time, based on intEnd, to start handling the nation.
    */
    function fAddNtnsToRqstQueue(value, index, array) {
      return {
        ntn: value.ntn,
        rqst: "&q=wa+name+region+unread+issues+census;scale=65;mode=score",
        pwrdtyp: value.pwrdtyp,
        pwrd: value.pwrd,
        when: dteNow + intEnd * Math.random()
      };
    }

    /* function to sort array by scheduled request time */
    function fSortWhen(a,b) {
      return a.when - b.when;
    }

    /* fGetXml takes the first request to be processed, then requests the XML from NS
       When the result is back, call fProcessXml.
    */
    function fGetXml() {
      if (aobjRqst.length > 0) {
        var objRqst = aobjRqst.shift();            // Take the first item in the queue
        var objXhttp = new XMLHttpRequest();                    // Create an object to send the details to NS
        objXhttp.onreadystatechange = function() {
          if (objXhttp.readyState == 4) {
            if (objXhttp.status == 200) {
              fProcessXml(this);            // When a response is received from NS, process it
            } else {
              if (objXhttp.status == 403) {
                strErr = objRqst.ntn + ": Forbidden (incorrect password?)<br>" + strErr;
                fGetNext();
              } else if (objXhttp.status == 404) {
                strErr = objRqst.ntn + ": Not Found (nation CTEd / misspelled / doesn't exist?)<br>" + strErr;
                fGetNext();
              } else if (objXhttp.status == 429) {
                strErr = objRqst.ntn + ": Too Many Requests.  Processing stopped<br>" + strErr;
              } else {
                strErr = objRqst.ntn + ": Error " + objXhttp.status + "<br>" + strErr;
              };
              document.getElementById("err").innerHTML = strErr;
            };
          };
        };
        // Set the details of the request:
        objXhttp.open("GET", "https://www.nationstates.net/cgi-bin/api.cgi?nation=" + objRqst.ntn + objRqst.rqst, true);
        if (objRqst.ntn == strNtnLast) {
          var i = 0;
          while (aobjNtns[i].ntn != strNtnLast) { i++; };         // Find the matching nation in aobjNtns
          objXhttp.setRequestHeader("X-Pin", aobjNtns[i].pin);         // If this request is for the same nation as the last,
          objXhttp.setRequestHeader(objRqst.pwrdtyp, objRqst.pwrd);      // use the Pin.  Send password as well, just in case
        } else {
          objXhttp.setRequestHeader(objRqst.pwrdtyp, objRqst.pwrd);      // Else set to use the password, and
          strNtnLast = objRqst.ntn;                  // record that this nation is now the latest nation
        };
        objXhttp.setRequestHeader("User-Agent", strUserAgent);         // Set to send the User-Agent too
        objXhttp.send();                     // Send the request to NS
      };
    }

    /* fGetNext sets up when to call fGetXML again */
    function fGetNext() {
      dteNow = new Date().getTime();
      if (aobjRqst.length > 0) {
        var intWhen = aobjRqst[0].when - dteNow;      // If there are more requests in the queue, work out when to send the next
        if (intWhen < 1200) { intWhen = 1200; };
        setTimeout(function(){ fGetXml() }, intWhen);
      } else {
        window.alert("Done");               // Else notify the user that the job is complete
      };
    }

    /* fProcess:
       Reads in the XML data.
       Updates the nation with the new Pin
       Works out when to call the routine that sends the next request
    */
    function fProcessXml(objXhttp) {
      // dteNow = new Date().getTime();
      var objXML = objXhttp.responseXML;               // Read in the body of the returned data
      var strNtn = objXML.getElementsByTagName("NATION")[0].getAttribute("id");   // Read the nation ID
      var i = 0;
      while (aobjNtns[i].ntn != strNtn) { i++; };            // Find the matching nation in aobjNtns
      var strPin = objXhttp.getResponseHeader("X-Pin");            // Read in the pin from returned data header
      if (strPin != null) { aobjNtns[i].pin = strPin; };         // Assign the pin to the nation
      if (objXML.getElementsByTagName("UNREAD").length > 0) {
        fProcessFirst(objXhttp, strNtn, i);         // If this was the first request for the nation, process accordingly
      } else {
        fProcessIssue(objXML, i);            // If this was the result of answering an issue, process accordingly
      };
      fGetNext();
    }

    /* fProcess reads in the XML data.
    */
    function fProcessFirst(objXhttp, strNtn, i) {
      var dteWhen = dteNow;
      var strTmp
      var objXML = objXhttp.responseXML;                     // Read in the body of the returned data
      aobjNtns[i].pwrdtyp = "X-Autologin";
      var strPwrd = objXhttp.getResponseHeader("X-Autologin");               // Read in the password from returned data header
      aobjNtns[i].pwrd = strPwrd;                        // Update the nation with the returned data
      aobjNtns[i].wa = objXML.getElementsByTagName("UNSTATUS")[0].childNodes[0].nodeValue;
      aobjNtns[i].rgn = objXML.getElementsByTagName("REGION")[0].childNodes[0].nodeValue;
      aobjNtns[i].ntc = objXML.getElementsByTagName("NOTICES")[0].childNodes[0].nodeValue;
      aobjNtns[i].tgs = objXML.getElementsByTagName("TELEGRAMS")[0].childNodes[0].nodeValue;
      aobjNtns[i].rmb = objXML.getElementsByTagName("RMB")[0].childNodes[0].nodeValue;
      aobjNtns[i].iss = objXML.getElementsByTagName("ISSUES")[0].childNodes[0].nodeValue;
      aobjNtns[i].spdr = Number(objXML.getElementsByTagName("SCORE")[0].childNodes[0].nodeValue);
      var aobjIss = objXML.getElementsByTagName("ISSUE");               // Create an array of the issues data
      var strOpt = "ignore";
      for (var j = 0; j < aobjIss.length; j++) {
        var aobjOpts = aobjIss[j].getElementsByTagName('OPTION');            // For each issue, find the options & choose response
        switch (aobjNtns[i].styl) {
          case "random":
            strOpt = aobjOpts[Math.floor(Math.random() * aobjOpts.length)].getAttribute('id');   // Choose a random answer
            break;
          case "1st":
            strOpt = aobjOpts[0].getAttribute('id');                  // Choose the first available answer
            break;
          case "2nd":
            strOpt = aobjOpts[1].getAttribute('id');                  // Choose the second available answer
            break;
          case "2nd last":
            strOpt = aobjOpts[aobjOpts.length - 2].getAttribute('id');            // Choose the second last answer
            break;
          case "last":
            strOpt = aobjOpts[aobjOpts.length - 1].getAttribute('id');            // Choose the last answer
            break;
        };                                 // Else the default treatment is to ignore issues
        if (strOpt != "ignore") {
          dteWhen = dteWhen + 20000 + 60000 * Math.random();               // Set the gap between issue answers to 20 to 80 seconds
          var objRqst = {
            ntn: strNtn,
            rqst: "&c=issue&issue=" + aobjIss[j].getAttribute("id") + "&option=" + strOpt,
            pwrdtyp: "X-Autologin",
            pwrd: strPwrd,
            when: dteWhen
          };
          aobjRqst.push(objRqst);                        // Add the nation, issue, and answer to the request queue
        };   
      };
      aobjRqst.sort(fSortWhen);                           // Make sure the request queue is in the right order     
      fSortNtns(strSort);                           // Update the visible HTML table
    }   

    /* fProcessIssue:
       Reduces the nation's issue count by one, if an issue was successfully answered
    */
    function fProcessIssue(objXML, i) {
      if (objXML.getElementsByTagName("OK").length > 0) {
        aobjNtns[i].iss = aobjNtns[i].iss - 1;            // If the issue was successfully answered, reduce the number of outstanding issues
        fSortNtns(strSort);                  // Update the visible HTML table
      };
    }

    function fSortNtns(strBy) {
      strSort = strBy; 
      switch (strBy) {
        case "ntn":
          aobjNtns.sort(fSortNtn);
          break;
        case "rgn":
          aobjNtns.sort(fSortRgn);
          break;
        case "ntc":
          aobjNtns.sort(fSortNtc);
          break;
        case "tgs":
          aobjNtns.sort(fSortTgs);
          break;
        case "rmb":
          aobjNtns.sort(fSortRmb);
          break;
        case "iss":
          aobjNtns.sort(fSortIss);
          break;
        case "spdr":
          aobjNtns.sort(fSortSpdr);
          break;
      };
      fUpdateTable();
    }

    /* function to sort array by nation */
    function fSortNtn(a,b) {
      var x = a.ntn.toLowerCase();
      var y = b.ntn.toLowerCase();
      if (x < y) { return -1; };
      if (x > y) { return 1; };
      return 0;
    }

    /* function to sort array by region */
    function fSortRgn(a,b) {
      var x = a.rgn.toLowerCase();
      var y = b.rgn.toLowerCase();
      if (x < y) { return -1; };
      if (x > y) { return 1; };
      return 0;
    }

    /* function to sort array by notices */
    function fSortNtc(a,b) {
      return b.ntc - a.ntc;
    }

    /* function to sort array by telegrams */
    function fSortTgs(a,b) {
      return b.tgs - a.tgs;
    }

    /* function to sort array by RMB posts */
    function fSortRmb(a,b) {
      return b.rmb - a.rmb;
    }

    /* function to sort array by Issues */
    function fSortIss(a,b) {
      return b.iss - a.iss;
    }

    /* function to sort array by SPDR */
    function fSortSpdr(a,b) {
      return b.spdr - a.spdr;
    }

    /*
    */
    function fUpdateTable() {
      var i;
      var strMsg = strHdr;                     // Set the table header
      var strNtc;
      var strTgs;
      var strRmb;
      var strIss;
      var strSpdr;
      for (i = 0; i < aobjNtns.length; i++) {
        if (aobjNtns[i].wa == "WA Member") {
          strMsg = strMsg + "<tr style='color:red;'>";            // Set to display any WA nation in red
        } else {
          strMsg = strMsg + "<tr>";
        };
        strNtc = aobjNtns[i].ntc;                  // If 'unread' counts are unknown, display them as "?"
        if (strNtc < 0) { strNtc = "?" };
        strTgs= aobjNtns[i].tgs;
        if (strTgs < 0) { strTgs = "?" };
        strRmb = aobjNtns[i].rmb;
        if (strRmb < 0) { strRmb = "?" };
        strIss = aobjNtns[i].iss;
        if (strIss < 0) { strIss = "?" };
        strSpdr = aobjNtns[i].spdr;
        if (strSpdr < 0) { strSpdr = "?" };
        strMsg = strMsg + "<td>"
          + aobjNtns[i].name + "</td><td>" + aobjNtns[i].rgn + "</td><td>"
          + strNtc + "</td><td>" + strTgs + "</td><td>"
          + strRmb + "</td><td>" + strIss + "</td><td>"
          + strSpdr + "</td></tr>";                  // Add details of each nation
      };
      document.getElementById("msg").innerHTML = strMsg;         // Output the complete data to the HTML table
    }
  </script>
</body>
</html>

PostPosted: Mon Nov 19, 2018 2:50 pm
by Wolfram and Hart
Sleeping Beauty updated following feedback. Thanks for the new ideas!

PostPosted: Mon Nov 19, 2018 5:28 pm
by Augustus Rex
As an ex-raider, thank you for this tool! Keeps the raiding fun w/o needing to answer drab issues