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>
This commit is contained in:
		
							parent
							
								
									d312674df7
								
							
						
					
					
						commit
						8762d68466
					
				
							
								
								
									
										24
									
								
								oracles/base.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								oracles/base.php
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								oracles/calc.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								oracles/calc.php
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								oracles/encoder.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								oracles/encoder.php
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								oracles/numerics.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								oracles/numerics.php
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								oracles/time.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								oracles/time.php
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								web.php
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								web.php
									
									
									
									
									
								
							| @ -475,6 +475,35 @@ if($c !== 0){ | |||||||
| 	$payload["left"] .= '</table>'; | 	$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 | 	Load next page | ||||||
| */ | */ | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 cynic
						cynic