From 8762d68466b825382f4708a0eebbd3074e32c5c5 Mon Sep 17 00:00:00 2001 From: cynic Date: Wed, 13 Sep 2023 22:24:02 -0500 Subject: [PATCH] add structure for `Oracles' (special answers depending on queries + a few implementations (#10) incl. a calculator, a hash encoder + rot13 and b64!, and a "what time is it" with timezone selection frontend injected in $payload["left"] in web.php you can see this live [on my instance](https://4get.silly.computer/web?s=7%2B8(9%5E2)&scraper=brave&nsfw=yes) (there are some issues that aren't related to this PR. favicons, etc. I'll fix them later.) Reviewed-on: https://git.lolcat.ca/lolcat/4get/pulls/10 Co-authored-by: cynic Co-committed-by: cynic --- oracles/base.php | 24 +++++++ oracles/calc.php | 158 +++++++++++++++++++++++++++++++++++++++++++ oracles/encoder.php | 40 +++++++++++ oracles/numerics.php | 54 +++++++++++++++ oracles/time.php | 44 ++++++++++++ web.php | 29 ++++++++ 6 files changed, 349 insertions(+) create mode 100644 oracles/base.php create mode 100644 oracles/calc.php create mode 100644 oracles/encoder.php create mode 100644 oracles/numerics.php create mode 100644 oracles/time.php diff --git a/oracles/base.php b/oracles/base.php new file mode 100644 index 0000000..45747fc --- /dev/null +++ b/oracles/base.php @@ -0,0 +1,24 @@ + "some oracle" + ]; + // this function should take in a query string search from $_GET, + // and return a bool determining whether or not it is a question + // intended for the oracle. + public function check_query($q) { + return false; + } + // produce the correct answer for the query using the oracle. + // note: if it becomes apparent /during generation/ that the + // query is not in fact for the oracle, returning an empty + // string will kill the oracle pane. + // answer format: ["ans1 title" => "ans1", ...] + public function generate_response($q) { + return ""; + } +} +?> \ No newline at end of file diff --git a/oracles/calc.php b/oracles/calc.php new file mode 100644 index 0000000..4888b95 --- /dev/null +++ b/oracles/calc.php @@ -0,0 +1,158 @@ + "calculator" + ]; + public function check_query($q) { + // straight numerics should go to that oracle + if (is_numeric($q)) { + return false; + } + // all chars should be number-y or operator-y + $char_whitelist = str_split("1234567890.+-/*^%() "); + foreach (str_split($q) as $char) { + if (!in_array($char, $char_whitelist)) { + return false; + } + } + return true; + } + // a custom parser and calculator because FUCK YUO, libraries are + // gay. + public function generate_response($q) + { + $nums = str_split("1234567890."); + $ops = str_split("+-/*^%;"); + $grouping = str_split("()"); + + $q = str_replace(" ", "", $q); + + // backstop for the parser so it catches the last + // numeric token + $q .= ";"; + + // the following comments refer to this example input: + // 21+9*(3+2^9)+1 + + // 2-length lists of the following patterns: + // ["n" (umeric), ] + // ["o" (perator), ""] + // ["g" (roup explicit), <"(" or ")">] + // e.g. [["n", 21], ["o", "+"], ["n", 9], ["o", *], + // ["g", "("], ["n", 3], ["o", "+"], ["n", 2], + // ["o", "^"], ["n", 9], ["g", ")"], ["o", "+"], + // ["n", "1"]] + $tokens = array(); + $dragline = 0; + foreach(str_split($q) as $i=>$char) { + if (in_array($char, $nums)) { + continue; + } + elseif (in_array($char, $ops) || in_array($char, $grouping)) { + // hitting a non-numeric implies everything since the + // last hit has been part of a number + $capture = substr($q, $dragline, $i - $dragline); + // prevent the int cast from creating imaginary + // ["n", 0] tokens + if ($capture != "") { + if (substr_count($capture, ".") > 1) { + return ""; + } + array_push($tokens, ["n", (float)$capture]); + } + // reset to one past the current (non-numeric) char + $dragline = $i + 1; + // the `;' backstop is not a real token and this should + // never be present in the token list + if ($char != ";") { + array_push($tokens, [ + ($char == "(" || $char == ")") ? "g" : "o", + $char + ]); + } + } + else { + return ""; + } + } + + // two operators back to back should fail + for ($i = 1; $i < count($tokens); $i++) { + if ($tokens[$i][0] == "o" && $tokens[$i-1][0] == "o") { + return ""; + } + } + + //strategy: + // traverse to group open (if there is one) + // - return to start with the internals + // traverse to ^, attack token previous and after + // same but for *, then / then + then - + // poppers all teh way down + try { + return [ + substr($q, 0, strlen($q)-1)." = " => $this->executeBlock($tokens)[0][1] + ]; + } + catch (\Throwable $e) { + if (get_class($e) == "DivisionByZeroError") { + return [ + $q." = " => "Division by Zero Error!!" + ]; + } + return ""; + } + } + public function executeBlock($tokens) { + if (count($tokens) >= 2 && $tokens[0][0] == "o" && $tokens[0][1] == "-" && $tokens[1][0] == "n") { + array_splice($tokens, 0, 2, [["n", -1 * (float)$tokens[1][1]]]); + } + if (count($tokens) > 0 && $tokens[0][0] == "o" || $tokens[count($tokens)-1][0] == "o") { + throw new Exception("Error Processing Request", 1); + } + if (in_array(["g", "("], $tokens)) { + $first_open = array_search(["g", "("], $tokens); + $enclosedality = 1; + for ($i = $first_open+1; $i < count($tokens); $i++) { + if ($tokens[$i][0] == "g") { + $enclosedality += ($tokens[$i][1] == "(") ? 1 : -1; + } + if ($enclosedality == 0) { + array_splice($tokens, + $first_open, + $i+1 - $first_open, + $this->executeBlock( + array_slice($tokens, $first_open+1, $i-1 - $first_open) + ) + ); + break; + } + } + } + $operators_in_pemdas_order = [ + "^" => (fn($x, $y) => $x ** $y), + "*" => (fn($x, $y) => $x * $y), + "/" => (fn($x, $y) => $x / $y), + "%" => (fn($x, $y) => $x % $y), + "+" => (fn($x, $y) => $x + $y), + "-" => (fn($x, $y) => $x - $y) + ]; + foreach ($operators_in_pemdas_order as $op=>$func) { + while (in_array(["o", $op], $tokens)) { + for ($i = 0; $i < count($tokens); $i++) { + if ($tokens[$i] == ["o", $op]) { + array_splice( + $tokens, + $i-1, + 3, + [["n", (string)($func((float)$tokens[$i-1][1], (float)$tokens[$i+1][1]))]] + ); + } + } + } + } + return $tokens; + } +} +?> \ No newline at end of file diff --git a/oracles/encoder.php b/oracles/encoder.php new file mode 100644 index 0000000..00b5ad0 --- /dev/null +++ b/oracles/encoder.php @@ -0,0 +1,40 @@ + "text encoder/hasher" + ]; + private $special_types = [ + "rot13", + "base64" + ]; + public function check_query($q) { + $types = array_merge($this->special_types, hash_algos()); + foreach ($types as $type) { + $type .= " "; + if (str_starts_with($q, $type)) { + return true; + } + } + return false; + } + public function generate_response($q) + { + $type = explode(" ", $q)[0]; + $victim = substr($q, strlen($type)+1); + if (in_array($type, hash_algos())) { + return [$type." hash" => hash($type, $victim)]; + } + switch ($type) { + case "rot13": + return ["rot13 encoded" => str_rot13($victim)]; + case "base64": + return [ + "base64 encoded" => base64_encode($victim), + "base64 decoded" => base64_decode($victim) + ]; + } + return ""; + } +} +?> \ No newline at end of file diff --git a/oracles/numerics.php b/oracles/numerics.php new file mode 100644 index 0000000..3d428e3 --- /dev/null +++ b/oracles/numerics.php @@ -0,0 +1,54 @@ + "numeric base conversion" + ]; + public function check_query($q) { + if (str_contains($q, " ")) { + return false; + } + + $q = strtolower($q); + + $profiles = [ + ["0x", str_split("0123456789abcdef")], + ["", str_split("1234567890")], + ["b", str_split("10")] + ]; + + foreach ($profiles as $profile) { + $good = true; + $good &= str_starts_with($q, $profile[0]); + $nq = substr($q, strlen($profile[0])); + foreach (str_split($nq) as $c) { + $good &= in_array($c, $profile[1]); + } + if ($good) { + return true; + } + } + return false; + } + public function generate_response($q) { + $n = 0; + if (str_starts_with($q, "0x")) { + $nq = substr($q, strlen("0x")); + $n = hexdec($nq); + } + elseif (str_starts_with($q, "b")) { + $nq = substr($q, strlen("b")); + $n = bindec($nq); + } + else { + $n = (int)$q; + } + return [ + "decimal (base 10)" => (string)$n, + "hexadecimal (base 16)" => "0x".(string)dechex($n), + "binary (base 2)" => "b".(string)decbin($n), + "" => "binary inputs should be prefixed with 'b', hex with '0x'." + ]; + } +} +?> \ No newline at end of file diff --git a/oracles/time.php b/oracles/time.php new file mode 100644 index 0000000..57af093 --- /dev/null +++ b/oracles/time.php @@ -0,0 +1,44 @@ + "what time is it?" + ]; + public function check_query($q) { + $prompts = [ + "what", "time", "is", "it", + "right", "now", "the", "current", + "get" + ]; + $q = str_replace(",", "", $q); + $q = str_replace("?", "", $q); + $q = str_replace("what's", "what is", $q); + $oq = $q; + $q = explode(" ", $q); + $count = 0; + foreach ($q as $word) { + if (in_array($word, $prompts)) { + $count++; + } + } + // remove one from total count if a timezone is specified + return ($count/(count($q) + (str_contains($oq, "tz:") ? -1 : 0))) > 3/4; + } + public function generate_response($q) { + $timezone = timezone_name_from_abbr("UTC"); + foreach (explode(" ", $q) as $word) { + if (str_starts_with($word, "tz:")) { + $decltz = timezone_name_from_abbr(substr($word, 3, 3)); + if ($decltz) { + $timezone = $decltz; + } + } + } + date_default_timezone_set($timezone); + return [ + "The time in ".$timezone => date("H:i:s"), + "" => "include the string \"tz:XXX\" to use timezone XXX" + ]; + } +} +?> \ No newline at end of file diff --git a/web.php b/web.php index 48539b9..e34672d 100644 --- a/web.php +++ b/web.php @@ -475,6 +475,35 @@ if($c !== 0){ $payload["left"] .= ''; } +/* + Prepend Oracle output, if applicable +*/ +include_once("oracles/encoder.php"); +include_once("oracles/calc.php"); +include_once("oracles/time.php"); +include_once("oracles/numerics.php"); +$oracles = [new calculator(), new encoder(), new time(), new numerics()]; +$fortune = ""; +foreach ($oracles as $oracle) { + if ($oracle->check_query($_GET["s"])) { + $resp = $oracle->generate_response($_GET["s"]); + if ($resp != "") { + $fortune .= "
"; + foreach ($resp as $title => $r) { + if ($title) { + $fortune .= "

".htmlspecialchars($title)."

".htmlspecialchars($r)."
"; + } + else { + $fortune .= "".$r."
"; + } + } + $fortune .= "Answer provided by oracle: ".$oracle->info["name"]."
"; + } + break; + } +} +$payload["left"] = $fortune . $payload["left"]; + /* Load next page */