Added Actions on Google code
diff --git a/api.php b/api.php
index fc1b6af..5bdbb00 100644
--- a/api.php
+++ b/api.php
@@ -12,13 +12,18 @@
   }
 }
 
+function get_graph() {
+  global $conf;
+  return json_decode(file_get_contents($conf["apiurl"]), true);
+}
+
 if (!isset($_GET["action"])) {
   write::error(1, "No action provided");
 }
 
 switch ($_GET["action"]) {
   case "getgraf":
-  $graf = file_get_contents("https://grafo.dirba.io/api.php?req=getGraph");
+  $graf = file_get_contents($conf["apiurl"]);
   echo $graf;
   break;
 
diff --git a/assistant/en.json b/assistant/en.json
new file mode 100644
index 0000000..cadd504
--- /dev/null
+++ b/assistant/en.json
@@ -0,0 +1,25 @@
+{
+  "new_to_graph": "It seems like {person} is new to the graph, because they don't share any edges with another person.",
+  "edges_1": "Alright, {person} shares an edge with {count}: {edges}.",
+  "edges_2": "Ok, {person} shares an edge with {count}: {edges}.",
+  "edges_3": "{person} shares an edge with {count}: {edges}.",
+  "edges_display": "{person} shares an edge with {count}.",
+  "count_singular": "one person",
+  "count_plural": "{count} people",
+  "not_found": "I'm sorry, but I didn't find anyone called {person} in the graph.",
+  "random_fact": "Here's a random fact: {fact}",
+  "num_vertices": "The number of vertices in the graph is {count}.",
+  "num_edges": "The number of edges in the graph is {count}.",
+  "didyouknow_last_edge": "did you know that the last edge is the one between {person1} and {person2}?",
+  "didyouknow_num_vertices": "Did you know that the number of vertices in the graph is {count}?",
+  "didyouknow_num_edges": "Did you know that the number of edges in the graph is {count}?",
+  "didyouknow_k3": "did you know that the first three vertices of the graph form a K3?",
+  "didyouknow_creator": "Did you know that Dario currently hosts the graph and that he is thought to be its creator?",
+  "didyouknow_groph": "did you know that for a while, the graph wasn't connex and the smallest component was named 'the groph'? Fortunately, right now the Groph is connected to the rest of the graph.",
+  "followup_1": "Is there anything else I can do for you?",
+  "followup_2": "Now, is there anything else you want to know?",
+  "followup_3": "Is there anything else you want to know?",
+  "latest_news": "<speak>These are the latest edges in the graph: {edges}.</speak>",
+  "latest_news_count": "<speak>These are the last {count} edges in the graph: {edges}.</speak>",
+  "and": "and"
+}
diff --git a/assistant/es.json b/assistant/es.json
new file mode 100644
index 0000000..e9ba01b
--- /dev/null
+++ b/assistant/es.json
@@ -0,0 +1,25 @@
+{
+  "new_to_graph": "Parece que {person} se añadió hace poco al grafo, porque no comparte ninguna arista con nadie.",
+  "edges_1": "Veamos, {person} comparte aristas con {count}: {edges}.",
+  "edges_2": "Vale, {person} comparte aristas con {count}: {edges}.",
+  "edges_3": "{person} comparte aristas con {count}: {edges}.",
+  "edges_display": "{person} comparte aristas con {count}.",
+  "count_singular": "una persona",
+  "count_plural": "{count} personas",
+  "not_found": "Lo siento, pero no he encontrado a nadie llamado {person} en el grafo.",
+  "random_fact": "Aquí tienes un dato curioso: {fact}",
+  "num_vertices": "El número de vértices en el grafo es de {count}",
+  "num_edges": "El número de aristas en el grafo es de {count}",
+  "didyouknow_last_edge": "¿sabías que la última arista es la que une {person1} y {person2}?",
+  "didyouknow_num_vertices": "¿Sabías que el número de vértices en el grafo es de {count}?",
+  "didyouknow_num_edges": "¿Sabías que el número de aristas en el grafo es de {count}?",
+  "didyouknow_k3": "¿sabías que los primeros tres vértices del grafo forman un K3?",
+  "didyouknow_creator": "¿Sabías que Dario hospeda actualmente el grafo y que se cree que él es su creador?",
+  "didyouknow_groph": "¿sabías que durante un tiempo, el grafo no era conexo y su componiente más pequeña se le llamó 'groph'? Afortunadamente, ahora el groph está conectado con la resta del grafo.",
+  "followup_1": "¿Hay algo más que pueda hacer por ti?",
+  "followup_2": "¿Tienes alguna otra pregunta?",
+  "followup_3": "¿Hay algo más que quieras saber?",
+  "latest_news": "<speak>Estos son los últimos vértices del grafo: {edges}.</speak>",
+  "latest_news_count": "<speak>Estos son los últimos {count} vértices del grafo: {edges}.</speak>",
+  "and": "y"
+}
diff --git a/assistant_callback.php b/assistant_callback.php
new file mode 100644
index 0000000..15b6f79
--- /dev/null
+++ b/assistant_callback.php
@@ -0,0 +1,248 @@
+<?php
+require_once("config.php");
+
+class write {
+  public static function do($json) {
+    echo json_encode($json)."\n";
+    exit();
+  }
+}
+
+class conv {
+  public static function ask($msg_array) {
+    $items = array();
+
+    foreach ($msg_array as $msg) {
+      $items[] = array("simpleResponse" => array("textToSpeech" => $msg));
+    }
+
+    self::ask_custom($items);
+  }
+
+  public static function ask_followup($msg, &$i18n) {
+    self::ask([$msg, $i18n->msg("followup_".mt_rand(1, 3))]);
+  }
+
+  public static function ask_custom($items) {
+    $json = array();
+    $json["payload"] = array();
+    $json["payload"]["google"] = array();
+    $json["payload"]["google"]["expectUserResponse"] = true;
+    $json["payload"]["google"]["richResponse"] = array();
+    $json["payload"]["google"]["richResponse"]["items"] = $items;
+
+    write::do($json);
+  }
+
+  public static function has($item, &$json) {
+    foreach ($json["originalDetectIntentRequest"]["payload"]["surface"]["capabilities"] as $cap) {
+      if ($cap["name"] == $item) {
+        return true;
+      }
+    }
+    return false;
+  }
+}
+
+class i18n {
+  public static $hllist = array("en", "es");
+  public $i18n_strings = null;
+  public $language = null;
+
+  function __construct($lang) {
+    global $_GET;
+    global $conf;
+
+    if (empty($this->i18n_strings)) {
+      $this->i18n_strings = array();
+    }
+
+    $this->language = $lang;
+
+    $this->i18n_strings = json_decode(file_get_contents("assistant/".$this->language.".json"), true);
+
+    return true;
+  }
+
+  function msg($message, $strings = null) {
+    if (!isset($this->i18n_strings[$message])) {
+      return false;
+    }
+
+    $string = $this->i18n_strings[$message];
+
+    if ($strings != null && is_array($strings)) {
+      foreach ($strings as $i => $subst) {
+        $string = str_replace("{".$i."}", $subst, $string);
+      }
+    }
+
+    return $string;
+  }
+}
+
+function get_graph() {
+  global $conf;
+  return json_decode(file_get_contents($conf["apiurl"]), true);
+}
+
+function comma($array, $wait=false) {
+  global $i18n;
+  if (count($array) == 0) {
+    return "";
+  }
+  if (count($array) == 1) {
+    return $array[0];
+  }
+  $break = ($wait ? '<break time="0.5s"/>' : "");
+  return implode($break.", ", array_slice($array, 0, -1)).$break.", ".$i18n->msg("and")." ".$array[count($array)-1];
+}
+
+$json = json_decode(file_get_contents('php://input'), true);
+
+if ($json === NULL || !isset($json["originalDetectIntentRequest"]) || !isset($json["originalDetectIntentRequest"]["source"]) || $json["originalDetectIntentRequest"]["source"] != "google") {
+  exit();
+}
+
+$graph = get_graph();
+$lang = (isset($json["queryResult"]["languageCode"]) && in_array(substr($json["queryResult"]["languageCode"], 0, 2), i18n::$hllist) ? substr($json["queryResult"]["languageCode"], 0, 2) : "en");
+$i18n = new i18n($lang);
+
+switch ($json["queryResult"]["intent"]["displayName"]) {
+  case "showVertex":
+  if (!isset($json["queryResult"]["parameters"]["Vertex"])) {
+    exit();
+  }
+  
+  $shortest = -1;
+  $closest = -1;
+  $closest_id = -1;
+
+  foreach ($graph["nodes"] as $id => $node) {
+    $lev = levenshtein(strtolower($json["queryResult"]["parameters"]["Vertex"]), strtolower($node["name"]));
+
+    if ($lev == 0) {
+      $closest = $node["name"];
+      $shortest = $lev;
+      $closest_id = $id;
+    }
+
+    if (($lev <= $shortest || $shortest < 0) && $lev <= 3) {
+      $closest = $node["name"];
+      $shortest = $lev;
+      $closest_id = $id;
+    }
+  }
+
+  if ($shortest != -1) {
+    $neighbors = array();
+    // We're suposing each vertex has a different name. If not, this would get rid of some of the edges, but if two vertexs had the same name there would be no way to differentiate them anyway, so I think it's ok.
+    foreach ($graph["edges"] as $edge) {
+      if ($edge["a"] == $closest_id) {
+        $neighbors[$graph["nodes"][$edge["b"]]["name"]] = $edge["votes"];
+      } elseif ($edge["b"] == $closest_id) {
+        $neighbors[$graph["nodes"][$edge["a"]]["name"]] = $edge["votes"];
+      }
+    }
+    if (count($neighbors) == 0) {
+      conv::ask_followup($i18n->msg("new_to_graph", array("person" => $closest)), $i18n);
+    } else {
+      arsort($neighbors);
+      $params = array("person" => $closest, "count" => (count($neighbors) == 1 ? $i18n->msg("count_singular") : $i18n->msg("count_plural", array("count" => count($neighbors)))));
+      /*if (conv::has("actions.capability.SCREEN_OUTPUT", $json)) {
+        $items = [
+          array(
+            "simpleResponse" => array(
+              "textToSpeech" => $i18n->msg("edges_display", $params)
+            )
+          ),
+          array(
+            "tableCard" => array(
+              "columnProperties" => [
+                array(
+                  "header" => "Adjacent vertex"
+                ),
+                array("header" => "Votes")
+              ],
+              "rows" => []
+            )
+          ),
+          array(
+            "simpleResponse" => array(
+              "textToSpeech" => $i18n->msg("followup_".mt_rand(1, 3))
+            )
+          )
+        ];
+        foreach ($neighbors as $neighbor => $votes) {
+          $items[1]["tableCard"]["rows"][] = array("cells" => [array("text" => $neighbor), array("text" => (string)$votes)]);
+        }
+        conv::ask_custom($items);
+      } else {*/ // This code shows a table if the user has a screen, but
+                 // unfortunately tables are not public yet.
+                 // @TODO: Uncomment when tables are out of developer preview.
+        $people = array_keys($neighbors);
+        $people_string = comma($people);
+        $params["edges"] = $people_string;
+        $num = mt_rand(1, 3);
+        conv::ask_followup($i18n->msg("edges_".$num, $params), $i18n);
+      /*}*/
+    }
+  } else {
+    conv::ask_followup($i18n->msg("not_found", array("person" => $json["queryResult"]["parameters"]["Vertex"])), $i18n);
+  }
+  break;
+
+  case "randomFact":
+  $rand = mt_rand(0, 5);
+  switch ($rand) {
+    case 0: // Last edge in the graph
+    $last = array_values(array_slice($graph["edges"], -1))[0];
+    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_last_edge", array("person1" => $graph["nodes"][$last["a"]]["name"], "person2" => $graph["nodes"][$last["b"]]["name"])))), $i18n);
+    break;
+
+    case 1: // Num vertices
+    conv::ask_followup($i18n->msg("didyouknow_num_vertices", array("count" => count($graph["nodes"]))), $i18n);
+    break;
+
+    case 2: // Num edges
+    conv::ask_followup($i18n->msg("didyouknow_num_edges", array("count" => count($graph["edges"]))), $i18n);
+    break;
+
+    case 3: // First 3 vertices K3
+    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_k3"))), $i18n);
+    break;
+
+    case 4: // Creator
+    conv::ask_followup($i18n->msg("didyouknow_creator"), $i18n);
+    break;
+
+    case 5: // Groph
+    conv::ask_followup($i18n->msg("random_fact", array("fact" => $i18n->msg("didyouknow_groph"))), $i18n);
+    break;
+  }
+  
+  break;
+
+  case "numVertexs":
+  conv::ask_followup($i18n->msg("num_vertices", array("count" => count($graph["nodes"]))), $i18n);
+  break;
+
+  case "numEdges":
+  conv::ask_followup($i18n->msg("num_edges", array("count" => count($graph["edges"]))), $i18n);
+  break;
+
+  case "latestNews":
+  $param = (isset($json["queryResult"]["parameters"]["numEdges"]) ? $json["queryResult"]["parameters"]["numEdges"] : null);
+  $num = (isset($param) && !empty($param) ? (int)$param : 4);
+  $last = array_values(array_slice($graph["edges"], -$num));
+  $edges = [];
+  foreach ($last as $edge) {
+    $edges[] = $graph["nodes"][$edge["a"]]["name"]." - ".$graph["nodes"][$edge["b"]]["name"];
+  }
+  $edges_string = comma($edges, true);
+  conv::ask_followup($i18n->msg((isset($param) && !empty($param) ? "latest_news_count" : "latest_news"), array("edges" => $edges_string, "count" => $num)), $i18n);
+  break;
+
+  default:
+  exit();
+}
diff --git a/img/graf_big.png b/img/graf_big.png
new file mode 100644
index 0000000..ba99f5f
--- /dev/null
+++ b/img/graf_big.png
Binary files differ
diff --git a/img/graf_square.png b/img/graf_square.png
new file mode 100644
index 0000000..c290bb0
--- /dev/null
+++ b/img/graf_square.png
Binary files differ
diff --git a/img/graf_square.svg b/img/graf_square.svg
new file mode 100644
index 0000000..14f51c7
--- /dev/null
+++ b/img/graf_square.svg
@@ -0,0 +1,28 @@
+<svg width="194" height="194" xmlns="http://www.w3.org/2000/svg">
+ <g>
+  <title>Layer 1</title>
+  <rect fill="#060606" stroke-width="0" id="svg_10" width="100%" height="100%"/>
+ </g>
+ <g>
+  <title>Layer 2</title>
+  <g id="svg_9">
+   <line fill-opacity="null" x1="48" y1="138" x2="98.5" y2="99" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
+   <line fill-opacity="null" x1="98.5" y1="99" x2="105" y2="150" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
+   <line fill-opacity="null" x1="53" y1="76" x2="149" y2="84" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
+   <line fill-opacity="null" x1="53" y1="76" x2="105" y2="150" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
+   <line fill-opacity="null" x1="98.5" y1="99" x2="149" y2="84" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
+   <line fill-opacity="null" x1="53" y1="76" x2="98.5" y2="99" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
+   <line fill-opacity="null" x1="53" y1="76" x2="121" y2="35" id="svg_1" fill="none" stroke="#FFF" stroke-width="2" />
+  </g>
+ </g>
+ <g>
+  <title>Layer 3</title>
+  <ellipse fill="#d61c08" cx="48" cy="138" id="svg_2" rx="8" ry="8"/>
+  <ellipse fill="#0159aa" cx="98.5" cy="99" id="svg_3" rx="8" ry="8"/>
+  <ellipse fill="#0159aa" cx="105" cy="150" id="svg_4" rx="8" ry="8"/>
+  <ellipse fill="#d61c08" cx="53" cy="76" id="svg_5" rx="8" ry="8"/>
+  <ellipse fill="#d61c08" cx="149" cy="84" id="svg_6" rx="8" ry="8"/>
+  <ellipse fill="#0159aa" cx="121" cy="35" id="svg_7" rx="8" ry="8"/>
+  <ellipse fill="#0ca80a" cx="147" cy="123" id="svg_8" rx="8" ry="8"/>
+  </g>
+</svg>