Procházet zdrojové kódy

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 <admin@cynic.moe>
Co-committed-by: cynic <admin@cynic.moe>
cynic před 2 roky
rodič
revize
8762d68466
6 změnil soubory, kde provedl 349 přidání a 0 odebrání
  1. 24 0
      oracles/base.php
  2. 158 0
      oracles/calc.php
  3. 40 0
      oracles/encoder.php
  4. 54 0
      oracles/numerics.php
  5. 44 0
      oracles/time.php
  6. 29 0
      web.php

+ 24 - 0
oracles/base.php

@@ -0,0 +1,24 @@
+<?php
+abstract class oracle {
+	// some info to spit out alongside the result, so the user knows
+	//  what exactly is giving out the answer. prevents confusion
+	//   about what oracle is answering them for ambiguous queries.
+	public $info = [
+		"name" => "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 "";
+	}
+}
+?>

+ 158 - 0
oracles/calc.php

@@ -0,0 +1,158 @@
+<?php
+include_once("oracles/base.php");
+class calculator extends oracle {
+	public $info = [
+		"name" => "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), <some number>]
+		//  ["o" (perator), "<some operator>"]
+		//  ["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;
+	}
+}
+?>

+ 40 - 0
oracles/encoder.php

@@ -0,0 +1,40 @@
+<?php
+include_once("oracles/base.php");
+class encoder extends oracle {
+	public $info = [
+		"name" => "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 "";
+	}
+}
+?>

+ 54 - 0
oracles/numerics.php

@@ -0,0 +1,54 @@
+<?php
+include_once("oracles/base.php");
+class numerics extends oracle {
+	public $info = [
+		"name" => "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'."
+		];
+	}
+}
+?>

+ 44 - 0
oracles/time.php

@@ -0,0 +1,44 @@
+<?php
+include_once("oracles/base.php");
+class time extends oracle {
+	public $info = [
+		"name" => "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"
+		];
+	}
+}
+?>

+ 29 - 0
web.php

@@ -475,6 +475,35 @@ if($c !== 0){
 	$payload["left"] .= '</table>';
 }
 
+/*
+	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 .= "<div class=\"infobox\">";
+			foreach ($resp as $title => $r) {
+				if ($title) {
+					$fortune .= "<h3>".htmlspecialchars($title)."</h3><div class=\"code\">".htmlspecialchars($r)."</div>";
+				}
+				else {
+					$fortune .= "<i>".$r."</i><br>";
+				}
+			}
+			$fortune .= "<small>Answer provided by oracle: ".$oracle->info["name"]."</small></div>";
+		}
+		break;
+	}
+}
+$payload["left"] = $fortune . $payload["left"];
+
 /*
 	Load next page
 */