// ==UserScript==
// @name           LJSupport: Admin console for requests
// @namespace      http://afunamatata.com/greasemonkey/
// @description    Quick access to the admin console when working on a support request
// @include        http://www.livejournal.com/support/see_request.bml*
// @tags           ljsupport
// ==/UserScript==

/* ===BEGIN XMLRPC.JS=== 

xmlrpc.js beta version 1
Tool for creating XML-RPC formatted requests in JavaScript

Copyright 2001 Scott Andrew LePera
scott@scottandrew.com
http://www.scottandrew.com/xml-rpc

License: 
You are granted the right to use and/or redistribute this 
code only if this license and the copyright notice are included 
and you accept that no warranty of any kind is made or implied 
by the author.

*/

function XMLRPCMessage(methodname){
  this.method = methodname||"system.listMethods";
  this.params = [];
  return this;
}

XMLRPCMessage.prototype.setMethod = function(methodName){
  if (!methodName) return;
  this.method = methodName;
}

XMLRPCMessage.prototype.addParameter = function(data){
  if (arguments.length==0) return;
  this.params[this.params.length] = data;
}

XMLRPCMessage.prototype.xml = function(){

  var method = this.method;
  
  // assemble the XML message header
  var xml = "";
  
  xml += "<?xml version=\"1.0\"?>\n";
  xml += "<methodCall>\n";
  xml += "<methodName>" + method+ "</methodName>\n";
  xml += "<params>\n";
  
  // do individual parameters
  for (var i = 0; i < this.params.length; i++){
    var data = this.params[i];
    xml += "<param>\n";

    xml += "<value>" + XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data),data) + "</value>\n";
    
    xml += "</param>\n";
  }
  
  xml += "</params>\n";
  xml += "</methodCall>";
  
  return xml; // for now
}

XMLRPCMessage.dataTypeOf = function (o){
  // identifies the data type
  var type = typeof(o);
  type = type.toLowerCase();
  switch(type){
    case "number":
      if (Math.round(o) == o) type = "i4";
      else type = "double";
      break;
    case "object":
      var con = o.constructor;
      if (con == Date) type = "date";
      else if (con == Array) type = "array";
      else type = "struct";
      break;
  }
  return type;
}

XMLRPCMessage.doValueXML = function(type,data){
  var xml = "<" + type + ">" + data + "</" + type + ">";
  return xml;
}

XMLRPCMessage.doBooleanXML = function(data){
  var value = (data==true)?1:0;
  var xml = "<boolean>" + value + "</boolean>";
  return xml;
}

XMLRPCMessage.doDateXML = function(data){
  var xml = "<dateTime.iso8601>";
  xml += dateToISO8601(data);
  xml += "</dateTime.iso8601>";
  return xml;
}

XMLRPCMessage.doArrayXML = function(data){
  var xml = "<array><data>\n";
  for (var i = 0; i < data.length; i++){
    xml += "<value>" + XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]),data[i]) + "</value>\n";
  }
  xml += "</data></array>\n";
  return xml;
}

XMLRPCMessage.doStructXML = function(data){
  var xml = "<struct>\n";
  for (var i in data){
    xml += "<member>\n";
    xml += "<name>" + i + "</name>\n";
    xml += "<value>" + XMLRPCMessage.getParamXML(XMLRPCMessage.dataTypeOf(data[i]),data[i]) + "</value>\n";
    xml += "</member>\n";
  }
  xml += "</struct>\n";
  return xml;
}

XMLRPCMessage.getParamXML = function(type,data){
  var xml;
  switch (type){
    case "date":
      xml = XMLRPCMessage.doDateXML(data);
      break;
    case "array":
      xml = XMLRPCMessage.doArrayXML(data);
      break;
    case "struct":
      xml = XMLRPCMessage.doStructXML(data);
      break;
	  case "boolean":
      xml = XMLRPCMessage.doBooleanXML(data);
      break;
    default:
      xml = XMLRPCMessage.doValueXML(type,data);
      break;
  }
  return xml;
}

function dateToISO8601(date){
  // wow I hate working with the Date object
  var year = new String(date.getYear());
  var month = leadingZero(new String(date.getMonth()));
  var day = leadingZero(new String(date.getDate()));
  var time = leadingZero(new String(date.getHours())) + ":" + leadingZero(new String(date.getMinutes())) + ":" + leadingZero(new String(date.getSeconds()));

  var converted = year+month+day+"T"+time;
  return converted;
} 
  
function leadingZero(n){
  // pads a single number with a leading zero. Heh.
  if (n.length==1) n = "0" + n;
  return n;
}

/* ===END XMLRPC.JS=== */

function xmlEncode(string) {
	return string.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('\'','&apos;').replace('"','&quot;');
}

function print(message, replace) {
  if(replace)
     document.getElementById("consolemessage").innerHTML = message;
  else
     document.getElementById("consolemessage").innerHTML += message;
}

function getOutput(response, result) {
  var object = new Object();
  var outputs = response.evaluate(".//member/name[text()='output']/parent::member/value//data/value/string", result, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);

  var message = "";
  for(var i = 0; i < outputs.snapshotLength; ) {
    message += "<div class='"+outputs.snapshotItem(i++).textContent + "'>" +
    outputs.snapshotItem(i++).textContent + "</div>";
  }
  print(message);
  object.message = message;
  return object;
}

function callback(details) {
  if(details.readyState == 4 && details.status == 200) {
    var response =  new DOMParser().parseFromString(details.responseText, "application/xml");
    var results = response.evaluate("//member/name[text()='results']/parent::member/value/array/data/value", response, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);

    if(results.snapshotLength == 0) {   
      print("<div class='error'>NFI what happened. Email afuna@lj with the exact command(s) you used.</div>", true);
      return;
    }

    endThrob("processing admin console commands");
    for(var i = 0; i < results.snapshotLength; ++i)    
      getOutput(response,results.snapshotItem(i));
  }
}

function sendRequest(data, host) {
  GM_xmlhttpRequest({
    method: "POST",
    url: "http://"+host+"/interface/xmlrpc",
    data: data,
    onload: callback,
    headers: { 
      'Content-Type' : 'text/xml',
      'X-LJ-Auth' : 'cookie'
    }
  });
}

function startThrob() {
  /*Thanks go to Ciaran for this image <3*/
  var throbberanim = "data:image/gif;base64," +
  "R0lGODlhEAAQAKECAGBgYLKysv///////yH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAA" +
  "EAAQAAACLpQvAMio2k4I6YnJJoaaSo10mReSl9lgYtStKYhFRjyj50ifuaiCpU7J4WwsYgEAIfkE" +
  "BQoAAwAsBgAAAAgABgAAAg5cMpl3ajwAWEhKZyeeBQAh+QQFCgADACwKAAIABgAIAAACDlwyeaeb" +
  "hl4C4FE6r60FACH5BAUKAAMALAoABgAGAAgAAAIPnBM2iMfaBgBqTmMp1qMAACH5BAUKAAMALAYA" +
  "CgAIAAYAAAIOnB1xmAowWngvOrpGXQUAIfkEBQoAAwAsAgAKAAgABgAAAg4cMJl3ajxCWEhKZyee" +
  "BQAh+QQFCgADACwAAAYABgAIAAACDhwweaebhl4K4VE6r60FACH5BAEKAAMALAAAAgAGAAgAAAIP" +
  "nAE2iMfaRghqTmMp1qMAADs=";
  
    print("Processing...<img src='" + throbberanim+"'>", true);

}

function endThrob(process) {
    print("<div>Done "+process+"!</done>", true);
}

function doSubmit(commands) {  

  startThrob();

  // var host = "goathack.theblob.org:8081";
  var host = "www.livejournal.com";
  var msg = new Object();
  msg.auth_method = "cookie"; 

  msg.username = document.evaluate("//form[@name='supportForm']//span[@class='ljuser']", document,null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.textContent;
  msg.commands = commands;
  
  var xmlrpc = new XMLRPCMessage("LJ.XMLRPC.consolecommand");  
  xmlrpc.addParameter(msg);
  sendRequest(xmlrpc.xml(), host);
}

function insertGoogleAPI() {

  if(document.getElementById("googletranslate")) return;
  
  // can I do an @require instead?
  script = document.createElement("script");
  script.src = "http://www.google.com/jsapi";
  script.id = "googletranslate";
  document.getElementsByTagName("head")[0].appendChild(script);
  script.addEventListener("readystatechange", function() { google.load("language", "1");}, false);
}

function doTranslate(text) {
  // how do I look this up? use @require instead?
  google = unsafeWindow.google;  
  // TODO: or look up [tag]?
  google.language.detect(text, function(result) {
    if (!result.error) {
      var language = 'unknown';
      for (l in google.language.Languages) {
        if (google.language.Languages[l] == result.language) {
          language = l;
          break;
        }
      }
      print(text + " is: " + language + "");
      
    }
  });  
}

insertionPoint = document.evaluate("//a[@name]|//p[@style]", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.previousSibling;

insertGoogleAPI();
var div = document.createElement("div");
div.setAttribute("id", "consolediv");
insertionPoint.appendChild(div);

var message = document.createElement("div");
message.setAttribute("id", "consolemessage")    
div.appendChild(message);

var form = document.createElement("form");
form.setAttribute("id", "consoleform");
div.appendChild(form);

var console = document.createElement("textarea");
console.setAttribute("id", "console");
console.setAttribute("rows", 3);
console.setAttribute("cols", 60);
form.appendChild(console);

// var preview = document.createElement("input");
// preview.setAttribute("type", "button");
// preview.setAttribute("value", "Preview");
// form.appendChild(preview);
// var count = 0;
// preview.addEventListener("click", function() {
//   var tokens = document.getElementById("console").value.split(/\s+/);
//   for(var i=0; i < tokens.length; ++i) {
//     token = tokens[i];
//   }
// }, false);

var submit = document.createElement("input");
submit.setAttribute("type", "button");
submit.setAttribute("value", "Submit");
form.appendChild(submit);
submit.addEventListener("click", function() {
  var input = document.getElementById("console").value.split("\n");
  var commands = new Array();
  for (var k in input) {
    if(input[k].replace(/\s+/, "").length != 0) commands.push(xmlEncode(input[k])); 
  }
  doSubmit(commands);  
}, false);

var translate = document.createElement("input");
translate.setAttribute("type", "button");
translate.setAttribute("value", "Translate");
translate.setAttribute("title", "Translated using Google");
form.appendChild(translate);
translate.addEventListener("click", function() {
  var input = document.getElementById("console").value;
  doTranslate(input);
}, false);

var refLink = document.createElement("a");
refLink.setAttribute("href", "http://www.livejournal.com/admin/console/reference.bml");
refLink.textContent = "reference";
form.appendChild(document.createTextNode(" "));
form.appendChild(refLink);

GM_addStyle("#consolediv {border: 3px black solid; padding: 2px; margin: 10px 0;} \
 #consolemessage div {font-weight: bold; font-size: 0.8em;} \
 .success {color: green; } .error {color: red;}");
