
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.
Edit: Version 1.2. Returns SPDR for each nation. Code skips over unrecognised / CTEd nations instead of stopping.
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.
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>