소스 검색

Initial release

The first public release of Goosle.
Arnan de Gans 1 년 전
부모
커밋
6e7114a205

BIN
apple-touch-icon.png


+ 198 - 0
assets/css/styles.css

@@ -0,0 +1,198 @@
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+
+body { position: relative; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; color: #222; background-color: #ffffff; line-height: 1; }
+div { margin: 0; padding: 0; border: 0; font-size: 100%; vertical-align: baseline; }
+article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; }
+h2, h3, h4, h5, h6 { font-weight: normal; }
+h2, h3, h4, h5, h6, p, ul, ol, blockquote { padding-top: .5em; padding-bottom: .5em; }
+ol, ol > li { margin: 0; padding: 0; list-style: none; }
+input, button { outline: none; }
+button { cursor: pointer; }
+p { font-size: 18px; color: #494949; }
+a { text-decoration: none; color: #4495d4; }
+a:hover { text-decoration: underline; }
+input[type="text"]:invalid ~ input[type="submit"] { opacity: 0.5; pointer-events: none; }
+
+/* Page structure */
+.wrap { min-height: 100vh; overflow: hidden; }
+.header-wrap { display: block; padding-top: 16px; width: 100%; }
+.results-wrap { position: relative; display: flex; margin: 0 158px 50px 158px; }
+.footer-wrap { position: absolute; bottom: 0; display: block; margin-top: 15px; padding: 0; width: 100%; }
+
+/* Main page */
+body.main { background-color: #1f242b; color: #f0f6fc; }
+.search-box-main, .password-generator { text-align: center; margin-top: 10%; }
+.search-box-main h1 { font-size: 70px; }
+.search-box-main .search, .password-generator .password { padding: 10px 20px; width: 600px; color: #f0f6fc; background-color: #333333; font-size: 32px; font-family: sans-serif; border: 1px solid #3C4043; border-radius: 10px; }
+.search-box-buttons button { margin: 30px 20px 10px 20px; padding: 13px 10px 13px 10px; min-width: 130px; color: #f0f6fc; background-color: #333333; font-size: 14px; border: 1px solid #1c1c1c; border-radius: 6px; }
+.search-box-buttons button:hover { border: 1px solid #5f6368; }
+
+.password-generator { margin: 30px auto; padding: 0; }
+.password-generator .password { margin: 10px auto; width: 300px; text-align: center; font-size: 14px; }
+
+/* Search Results - Header */
+.header-wrap { background-color: #1f242b; color: #f0f6fc; border-bottom: 2px solid #1fa4d1; }
+.header-wrap .search { position: relative; margin: 28px 0 28px 158px; padding: 5px 5px 5px 15px; height: 30px; width: 580px; color: #f0f6fc; background-color: #1f242b; font-size: 100%; font-weight: 400; border: 1px solid #303842; border-radius: 10px 0 0 10px; }
+.header-wrap .button { position: relative; margin: 28px 10px 28px 0; padding: 5px 20px 5px 15px; height: 40px; color: #f0f6fc; background-color: #1fa4d1; font-size: 100%; font-weight: 400; border: none; border-radius: 0 10px 10px 0; }
+
+/* Search results - Header Navigation */
+.navigation-header { margin-left: 165px; margin-bottom: 10px; }
+.navigation-header img { margin-right: 5px; height: 16px; vertical-align: middle; }
+.navigation-header a { margin-right: 20px; border: none; font-size: 16px; cursor: pointer; text-decoration: none; }
+.navigation-header a:hover { color: #ebf3fa; }
+.navigation-header a:visited { color: #1fa4d1; }
+.navigation-header .active { padding-bottom: 8px; border-bottom: 4px #1fa4d1 solid; }
+
+/* Search results - Main column */
+.main-column { width: 100%; }
+.main-column ol .result { margin: .50rem 0 .50rem 0; padding: 0; }
+.main-column ol .special-result, .main-column ol .meta-time, .main-column ol .meta-error, .main-column ol .meta-did-you-mean { margin: .75rem 0 .75rem 0; padding: .5rem 10px; }
+
+.main-column ol li article { padding: .5rem 10px; border: 1px solid #fefefe; border-radius: 8px; }
+.main-column ol li article div.url:first-child, .main-column ol li.special-result article div.source:first-child { flex-grow: 0; }
+.main-column ol li article div.url { display: inline-block; margin: 0; max-width: 100%; color: #666; font-size: 1rem; line-height: 1.6; letter-spacing: .2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.main-column ol li article div.url a { margin: 0; color: #3f6e35; cursor: pointer; text-decoration: none; }
+.main-column ol li article div.title, .main-column ol li.special-result article div.title { margin-top: .146rem; margin-bottom: .28451rem; }
+.main-column ol li article div.title h2 { margin: 0; padding: 0; position: relative; font-size: 1.46rem; letter-spacing: -.01px; }
+.main-column ol li article div.title h2:hover { text-decoration: underline; }
+.main-column ol li article div.title a { margin: 0; display: block; cursor: pointer; }
+.main-column ol li article div.title a:visited { color: #6d59a3; }
+.main-column ol li article div.description { margin: 0; line-height: 1.4; font-size: 1rem; color: #494949; }
+.main-column ol li article div.description .seeders { color: #518257; }
+.main-column ol li article div.description .leechers { color: #c00; }
+
+/* Image results - Main column */
+.main-column .image-wrapper { width: 100%; margin: .75rem 0 .75rem 0; }
+@supports not (display: grid) {
+	.image-grid > * { max-width: 8rem; margin-left: auto; margin-right: auto; }
+	.image-grid li.result { display: inline-block; margin: .75rem; width: 12.5%; }
+	.image-grid > * + * { margin-top: 1rem; }
+}
+@supports (display: grid) {
+	.main-column ol.image-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr)); grid-gap: 1rem; }
+}
+.main-column ol.image-grid .result .image-box { position: relative; }
+.main-column ol.image-grid .result .image-box::after { display: block; padding-bottom: 100%; content: ""; }
+.main-column ol.image-grid .result .image-box img { position: absolute; object-fit: cover; width: 100%; height: 100%; }
+
+/* Special results - Main column */
+.main-column ol .special-result { background-color: #fefefe; }
+.main-column ol li.special-result article { padding: .5rem 10px; position: relative; overflow: hidden; border: 1px solid #aeaeae; border-radius: 8px; color: #222; }
+.main-column ol li.special-result article div.title h2 { margin: 0; padding: 0; font-size: 1.5rem; font-weight: 600; word-wrap: break-word; color: #222; }
+.main-column ol li.special-result article div.title h2:hover { text-decoration: none; }
+.main-column ol li.special-result article div.title a { margin: 0; display: block; color: #6c00a2; cursor: pointer; }
+.main-column ol li.special-result article div.title a:visited { color: #6d59a3; }
+.main-column ol li.special-result article div.text, .main-column ol li article div.source { padding-top: 10px; position: relative; font-style: normal; }
+.main-column ol li.special-result article div.text img { padding: 0 0 10px 10px; }
+.main-column ol li.special-result article div.source { display: inline-block; margin: 0; max-width: 100%; color: #666; font-size: 1rem; line-height: 1.6; letter-spacing: .2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; text-decoration: inherit; }
+.main-column ol li.special-result article div.source a { margin: 0; color: #3f6e35; text-decoration: none; }
+
+/* Misc */
+.logo { position: absolute; margin: 28px 18px; }
+.logo a { color: #f0f6fc; cursor: pointer; }
+.no-decoration, .no-decoration:hover { text-decoration: none; }
+.hide { display: none; }
+.G { color: #1fa4d1; }
+.meta-error { position: relative; overflow: hidden; color: #c00; background-color: #ffebe8; border: 1px solid #c00; border-radius: 8px; }
+.auth-error { margin-top: 15%; font-size: 32px; text-align: center; color: #eaeaea; }
+
+/* Footer bar */
+.footer-wrap { background-color: #161616; color: #f0f6fc; border-top: 2px solid #303134; }
+.footer { padding: 10px; }
+.footer a { color: #f0f6fc; }
+
+@media only screen and (max-width:960px)  { /* tablet, landscape iPad, lo-res laptops ands desktops */ 
+	/* Page structure */
+	.results-wrap { position: relative; display: flex; margin: 0 48px 20px 48px; }
+
+	/* Main page */
+	.search-box-main { margin-top: 10%; }
+	.search-box-main input { width: 80%; }
+	.search-box-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
+
+	/* Search Results - Header */
+	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
+	.header-wrap .search { margin: 10px 0px 28px 48px; width: 400px; }
+	.header-wrap .search, .header-wrap .button { margin: 10px 0px 28px 0px; }
+	
+	/* Search results - Header Navigation */
+	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
+	.navigation-header a { margin: 0 auto; padding: 0; }
+
+	/* Misc */
+	.logo { position: relative; display: block;margin: 0 auto; float: none; padding: 10px; font-size: 28px; }
+}
+
+@media only screen and (max-width:640px)  { /* portrait tablets, portrait iPad, landscape e-readers, landscape 800x480 or 854x480 phones */ 
+	/* Page structure */
+	.results-wrap { position: relative; display: flex; margin: 0 10px 10px 10px; }
+
+	/* Main page */
+	.search-box-main { margin-top: 10%; }
+	.search-box-main input { width: 80%; }
+	.search-box-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
+
+	/* Search Results - Header */
+	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
+	.header-wrap .search, .header-wrap .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
+	
+	/* Search results - Header Navigation */
+	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
+	.navigation-header a { margin: 0 auto; padding: 0; }
+
+	/* Misc */
+	.logo { position: relative; display: block; float: none; margin: 0 auto; padding: 10px; font-size: 28px; }
+}
+
+@media only screen and (max-width:480px)  { /* portrait e-readers (Nook/Kindle), smaller tablets @ 600 or @ 640 wide. */ 
+	/* Page structure */
+	.results-wrap { position: relative; display: flex; margin: 0 10px 10px 10px; }
+
+	/* Main page */
+	.search-box-main { margin-top: 10%; }
+	.search-box-main input { width: 80%; }
+	.search-box-main h1 { font-size: 45px; }
+	.search-box-buttons button { display: table-row; margin: 30px 0px 0px 0px; width: 80%; }
+
+	/* Search Results - Header */
+	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
+	.header-wrap .search, .header-wrap .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
+	
+	/* Search results - Header Navigation */
+	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
+	.navigation-header a { margin: 0 auto; padding: 0; }
+
+	/* Misc */
+	.logo { position: relative; display: block; float: none; margin: 0 auto; padding: 10px; font-size: 28px; }
+}
+
+@media only screen and (max-width:320px)  { /* smartphones, iPhone, portrait 480x320 phones */ 
+	/* Page structure */
+	.results-wrap { position: relative; display: flex; margin: 0 10px 10px 10px; }
+
+	/* Main page */
+	.search-box-main { margin-top: 40px; }
+	.search-box-main input { width: 80%; }
+	.search-box-main h1 { font-size: 45px; }
+	.search-box-buttons button { display: table-row; margin: 20px 0px 0px 0px; width: 80%; }
+
+	/* Search Results - Header */
+	.header-wrap { margin-left: auto; margin-right: auto; text-align: center; }
+	.header-wrap .search, .header-wrap .button { margin: 0px 0px 10px 0px; width: 80%; border-radius: 25px; }
+	
+	/* Search results - Header Navigation */
+	.navigation-header { display: flex; margin: 0; padding: 0; align-items: baseline; }
+	.navigation-header a { margin: 0 auto; padding: 0; }
+
+	/* Misc */
+	.logo { position: relative; float: none; display: block; margin: 0 auto; padding: 10px; font-size: 28px; }
+}

BIN
assets/images/image.png


BIN
assets/images/search.png


BIN
assets/images/torrent.png


+ 114 - 0
config.php

@@ -0,0 +1,114 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+
+/* ------------------------------------------------------------------------------------
+HASH:
+	A simple lowercase passphrase (something simple like: j9fg-i2du-er6m or 1846) used for caching results. This helps to differentiate between instances on the same server.
+	You can also add it to your url/bookmark as a simple passphrase to keep unwanted users out.
+
+CACHE:
+	If you have ACPu it is highly recommended to enable caching as it'll speed up repeatable searched by a lot.
+	"on" (Recommended) for active sites, requires APCu
+	"off" Disables cache, useful for testing or if your server lacks APCu support
+
+CACHE_TIME:
+	Minutes the result should be cached in ACPu.
+
+HASH AUTH:
+	Use the above hash as a simple passphrase.
+	Using it as a passphrase lets you host Goosle on a public server without providing a public service.
+
+	Usage: https://example.com/?a=1234567890
+	Disclaimer: This is not meant to 'hack proof' or truly secure the setup. Just a simple token to keep surface level prying eyes out.
+	
+RAW OUTPUT:
+	"off" (Recommended) for active sites
+	"on" Output the search results as an array instead of formatted with HTML
+  
+ENABLE TORRENT SEARCH:
+	Enable or disable searching for torrent downloads.
+	"on" (Default)
+	"off"
+
+ENABLE IMAGE SEARCH:
+	Enable or disable image searches - Search results are provided by Qwant.
+	"on" (Default)
+	"off"
+
+SPECIAL:
+	Enable or disable special searches that show up before search results.
+	"on" (Default)
+	"off" Disable this special search
+
+USER AGENTS:
+	Add more or less user agents to the list. Keep at least one.
+	On every search Goosle picks one at random to identify as.
+	Keep them generic to prevent profiling, but also so that the request comes off as a generic boring browser and not a server/script.
+	
+	Safari and Internet Explorer may be a limiting factor on results as they are lesser supported browsers. But should otherwise be fine.
+	Chrome may attract attention because of the lack of Chrome information (tracking) aside from the user agent.
+	Opera/Edge/Brave and many others use Chrome under the hood and are not a good pick for that reason.
+
+BLOCK 1337x CATEGORIES:
+	Add category IDs of 1337x categories, check /engines/torrent/1337x.php for a list of known categories.
+
+BLOCK PIRATEBAY CATEGORIES:
+	Add category IDs of Pirate Bay categories, check /engines/torrent/thepiratebay.php for a list of known categories.
+
+BLOCK YTS CATEGORIES:
+	Add category names as keywords, eg; "thriller", "war".
+	Movies can be in multiple categories, if a movie is in 5 categories it only has to match one to be filtered out.
+
+TORRENT TRACKERS:
+	Only used for The Pirate Bay and YTS.
+	These are added to the magnet links Goosle creates. You can add more or replace the existing ones.
+------------------------------------------------------------------------------------ */
+
+return (object) array(
+	"hash" => "blja-3jaq-34eg",
+    "cache" => "off",
+    "cache_time" => 30, // (Default: 30)
+    "hash_auth" => "off", // Default: off)
+    "raw_output" => "off", // (Default: off)
+
+    "enable_torrent_search" => "on", // (Default: on)
+    "enable_image_search" => "on", // (Default: on)
+
+	"special" => array(
+		"currency" => "on", // Currency converter
+		"definition" => "on", // Word dictionary
+		"wikipedia" => "on", // Wikipedia highlight
+		"phpnet" => "on", // PHP-dot-net highlight
+		"password_generator" => "on" // Password generator on homepage
+	),
+
+	"user_agents" => array(
+		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15) Gecko/20100101 Firefox/119.0", // macOS 10.15, FF 119
+		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/116.0", // Windows 10, FF 116
+		"Mozilla/5.0 (X11; Ubuntu; Linux x86_64) Gecko/20100101 Firefox/83.0", // Linux Ubuntu, FF 83
+		"Mozilla/5.0 (X11; Linux i686) Gecko/20100101 Firefox/119.0", // Linux Generic, FF 119
+	),
+
+    "leetx_categories_blocked" => array(3, 7, 47), // Default: 3, 7, 47
+    "piratebay_categories_blocked" => array(206, 210), // Default: 206, 210
+    "yts_categories_blocked" => array("horror"), // Default: "horror"
+    "torrent_trackers" => array(
+    	"http://nyaa.tracker.wf:7777/announce", 
+    	"udp://open.stealth.si:80/announce", 
+    	"udp://tracker.opentrackr.org:1337/announce", 
+    	"udp://exodus.desync.com:6969/announce", 
+    	"udp://tracker.torrent.eu.org:451/announce",
+    ),
+    "version" => "1.0",
+    "released" => "Nov 28, 2023"
+);
+?>

+ 93 - 0
engines/duckduckgo.php

@@ -0,0 +1,93 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class DuckDuckGoRequest extends EngineRequest {
+    public function get_request_url() {
+        $results = array();
+
+		// Split the query
+	    $query_terms = explode(" ", strtolower($this->query));
+
+		// Safe search override
+		$safe = "-1";
+		if(strpos($query_terms[0], "safe") !== false) {
+			$switch = explode(":", $query_terms[0]);
+
+			if(!is_numeric($switch[1])) {
+				$safe = ($switch[1] == "on") ? "1" : "-2";
+
+				$this->query = implode(" ", array_slice($query_terms, 1));
+			}
+		}
+
+		// q = query
+		// kz = Instant answers (1 = on, -1 = off)
+		// kc = Autoload images (1 = on, -1 = off)
+		// kav = Autoload results (1 = on, -1 = off)
+		// kf = Favicons (1 = on, -1 = off)
+		// kaf = Full URLs (1 = on, -1 = off)
+		// kac = Auto suggest (1 = on, -1 = off)
+		// kd = Redirects (1 = on, -1 = off)
+		// kh = HTTPS (1 = on, -1 = off)
+		// kg = Get/Post (g = GET, p = POST)
+		// kp = Safe search  (1 = on, -1 = moderate, -2 = off (may include nsfw/illegal content))
+		// k1 = Ads (1 = on, -1 = off)
+		// More here: https://duckduckgo.com/duckduckgo-help-pages/settings/params/
+
+		$args = array("q" => $this->query, "kz" => "-1", "kc" => "-1", "kav" => "-1", "kf" => "-1", "kaf" => "1", "kac" => "-1", "kd" => "-1", "kh" => "1", "kg" => "g", "kp" => $safe, "k1" => "-1");
+        $url = "https://html.duckduckgo.com/html/?".http_build_query($args);
+
+        unset($query_terms, $switch, $args, $safe);
+
+        return $url;
+    }
+
+    public function parse_results($response) {
+		$results = array();
+		$xpath = get_xpath($response);
+
+		if(!$xpath) return $results;
+ 
+		$didyoumean = $xpath->query(".//div[@id='did_you_mean']/a[1]")[0];
+		if(!is_null($didyoumean)) {
+			array_push($results, array("did_you_mean" => $didyoumean->textContent));
+		}
+        $search_specific = $xpath->query(".//div[@id='did_you_mean']/a[2]")[0];
+        if(!is_null($search_specific)) {
+            array_push($results, array("search_specific" => $search_specific->textContent));
+        }
+ 
+        foreach($xpath->query("/html/body/div[1]/div[". count($xpath->query('/html/body/div[1]/div')) ."]/div/div/div[contains(@class, 'web-result')]/div") as $result) {
+            $url = $xpath->evaluate(".//h2[@class='result__title']//a/@href", $result)[0];
+            if($url == null) continue;
+
+            if(!empty($results)) { // filter duplicate urls/results
+		        $result_urls = array_column($results, "url");
+                if(in_array($url->textContent, $result_urls) || in_array(get_base_url($url->textContent), $result_urls)) continue;
+            }
+
+			$title = $xpath->evaluate(".//h2[@class='result__title']", $result)[0];
+			if($title == null) continue;
+
+			$description = $xpath->evaluate(".//a[@class='result__snippet']", $result)[0];
+
+            array_push($results, array (
+                "title" => htmlspecialchars($title->textContent),
+                "url" =>  htmlspecialchars($url->textContent),
+                "description" =>  $description == null ? 'No description was provided for this site.' : htmlspecialchars($description->textContent)
+            ));
+		}
+
+		return $results;
+    }
+
+}
+?>

+ 112 - 0
engines/google.php

@@ -0,0 +1,112 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class GoogleRequest extends EngineRequest {
+    public function get_request_url() {
+        $results = array();
+
+		// Split the query
+	    $query_terms = explode(" ", strtolower($this->query));
+	
+		// Category search
+		$cat = null;
+		if($query_terms[0] == 'app') {
+			$cat = 'app';
+		} else if($query_terms[0] == 'book') {
+			$cat = 'bks';
+		} else if($query_terms[0] == 'news') {
+			$cat = 'nws';
+		} else if($query_terms[0] == 'shop') {
+			$cat = 'shop';
+		} else if($query_terms[0] == 'patent') {
+			$cat = 'pts';
+		}
+		
+		// Language override
+		$lang = null;
+		if(strpos($query_terms[0], "lang") !== false) {
+			$switch = explode(":", $query_terms[0]);
+
+			if(strlen($switch[1]) == 2 && !is_numeric($switch[1])) {
+				$lang = "lang_".$switch[1];
+
+				$this->query = implode(" ", array_slice($query_terms, 1));
+			}
+		}
+
+		// Safe search override
+		$safe = 1;
+		if(strpos($query_terms[0], "safe") !== false) {
+			$switch = explode(":", $query_terms[0]);
+
+			if(!is_numeric($switch[1])) {
+				$safe = ($switch[1] == "on") ? "2" : "0";
+
+				$this->query = implode(" ", array_slice($query_terms, 1));
+			}
+		}
+
+		// q = query
+		// safe = Safe search (Default 1) 0 = off (may include nsfw/illegal content), 1 = moderate, 2 = on/strict
+		// lr = Language (lang_XX, optional)
+		// tbm = Category search (app, bks (books), isch (images), vid, nws (News), shop, pts (Patents))
+		// pws = Personal search results on|off (Default 0, off)
+		// num = Number of results per page (number, multiple on 10 usually)
+		// start = Start position in search (Kind of like pages) (number, multiple on 10|15 usually)
+
+		$args = array("q" => $this->query, "safe" => $safe, "lr" => (!is_null($lang)) ? $lang : '', "tbm" => (!is_null($cat)) ? $cat : '', "pws" => 0, "num" => 20, "start" => 0);
+        $url = "https://www.google.com/search?".http_build_query($args);
+
+        unset($query_terms, $switch, $args, $cat, $lang, $safe);
+
+        return $url;
+    }
+
+    public function parse_results($response) {
+        $results = array();
+        $xpath = get_xpath($response);
+
+        if(!$xpath) return $results;
+
+        $didyoumean = $xpath->query(".//a[@class='gL9Hy']")[0];
+        if(!is_null($didyoumean)) {
+            array_push($results, array("did_you_mean" => $didyoumean->textContent));
+        }
+        $search_specific = $xpath->query(".//a[@class='spell_orig']")[0];
+        if(!is_null($search_specific)) {
+            array_push($results, array("search_specific" => $search_specific->textContent));
+        }
+
+        foreach($xpath->query("//div[@id='search']//div[contains(@class, 'g')]") as $result) {
+            $url = $xpath->evaluate(".//div[@class='yuRUbf']//a/@href", $result)[0];
+			if($url == null) continue;
+
+            if(!empty($results)) { // filter duplicate urls/results
+		        $result_urls = array_column($results, "url");
+                if(in_array($url->textContent, $result_urls) OR in_array(get_base_url($url->textContent), $result_urls)) continue;
+            }
+
+			$title = $xpath->evaluate(".//h3", $result)[0];
+			if($title == null) continue;
+
+			$description = $xpath->evaluate(".//div[contains(@class, 'VwiC3b')]", $result)[0];
+
+            array_push($results, array (
+                "title" => htmlspecialchars($title->textContent),
+                "url" =>  htmlspecialchars($url->textContent),
+                "description" =>  $description == null ? 'No description was provided for this site.' : htmlspecialchars($description->textContent)
+            ));
+        }
+
+        return $results;
+    }
+}
+?>

+ 117 - 0
engines/search-image.php

@@ -0,0 +1,117 @@
+<?php
+class ImageSearch extends EngineRequest {
+
+	public function get_request_url() {
+        $results = array();
+
+		// Split the query
+	    $query_terms = explode(" ", strtolower($this->query));
+
+		// Size override
+		$size = "";
+		if($query_terms[0] == 'size') {
+			$switch = explode(":", $query_terms[0]);
+
+			if((strlen($switch[1]) >= 3 && strlen($switch[1]) <= 6) && !is_numeric($switch[1])) {
+				if($switch[1] == "med") $switch[1] = "medium";
+				if($switch[1] == "lrg") $switch[1] = "large";
+	
+				if($switch[1] == "small" || $switch[1] == "medium" || $switch[1] == "large") {
+					$size = $switch[1];
+				}
+				
+				$this->query = implode(" ", array_slice($query_terms, 1));
+			}
+		}
+
+		// q = query
+		// t = Search type (images)
+		// size = Preferred image size (small|medium|large)
+
+		$args = array("q" => $this->query, "t" => "images", "size" => $size);
+        $url = "https://lite.qwant.com?".http_build_query($args);
+
+        unset($query_terms, $switch, $args, $size);
+
+        return $url;
+	}
+	
+	public function parse_results($response) {
+		$results = array("search" => array());
+		$xpath = get_xpath($response);
+	
+		if(!$xpath) return $results;
+
+		foreach($xpath->query("//a[@rel='noopener']") as $result) {
+			$meta = $xpath->evaluate(".//img", $result)[0];
+
+			if($meta) {
+				$encoded_url = explode("?position", explode("==/", $result->getAttribute("href"))[1])[0];
+
+				$url = htmlspecialchars(urldecode(base64_decode($encoded_url)));
+				$alt_text = get_base_url($url)." - ".htmlspecialchars($meta->getAttribute("alt"));
+				$image = urldecode(htmlspecialchars(urlencode($meta->getAttribute("src"))));
+				
+				// filter duplicate urls/results
+	            if(!empty($results)) {
+			        $result_urls = array_column($results, "url");
+	                if(in_array($url, $result_urls) || in_array(get_base_url($url), $result_urls)) continue;
+	            }
+
+				array_push($results["search"], array (
+					"alt" => $alt_text,
+					"image" => $image,
+					"url" => $url,
+				));
+			}
+		}
+
+		// Add warning if there are no results, or a text if there is no search query.
+		if(empty($results['search'])) {
+			$results["error"] = array(
+				"message" => "No results found. Please try with less or different keywords!"
+			);
+		}
+		
+		return $results;
+	}
+	
+	public static function print_results($results, $opts) {
+		if($opts->raw_output == "on") {
+			echo '<pre>Results: ';
+			print_r($results);
+			echo '</pre>';
+		}
+
+		echo "<section class=\"main-column\">";
+		echo "<ol>";
+
+		// Elapsed time
+		if(array_key_exists("search", $results)) {
+			echo "<li class=\"meta-time\">Fetched the results in ".$results['time']." seconds.</li>";
+		}
+
+        echo "</ol>";
+
+		echo "<div class=\"image-wrapper\">";
+		echo "<ol class=\"image-grid\">";
+	
+		// Search results
+		if(array_key_exists("search", $results)) {
+	        foreach($results['search'] as $result) {
+				if(!array_key_exists("url", $result) || !array_key_exists("alt", $result)) continue;
+
+				// Put result together
+				echo "<li class=\"result\"><div class=\"image-box\">";
+				echo "<a href=\"".$result["url"]."\" target=\"_blank\" title=\"".$result["alt"]."\"><img src=\"".$result["image"]."\" alt=\"".$result["alt"]."\" /></a>";
+				echo "</div></li>";
+	        }
+		}
+
+        echo "</ol>";
+        echo "</div>";
+ 		echo "<center><small>Not what you're looking for? Try <a href=\"https://duckduckgo.com/?q=".urlencode($opts->query)."&iax=images&ia=images\" target=\"_blank\">DuckDuckGo</a> or <a href=\"https://www.google.com/search?q=".urlencode($opts->query)."&tbm=isch&pws=0\" target=\"_blank\">Google Images</a>.</small></center>";
+ 		echo "</section>";
+	}
+}
+?>

+ 109 - 0
engines/search-torrent.php

@@ -0,0 +1,109 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class TorrentSearch extends EngineRequest {
+	protected $requests;
+	
+	public function __construct($opts, $mh) {
+        $this->opts = $opts;
+		$this->url = 'torrent'; // Dummy value to satisfy EngineRequest::get_results()
+
+		require "engines/torrent/1337x.php";
+		require "engines/torrent/nyaa.php";
+		require "engines/torrent/thepiratebay.php";
+		require "engines/torrent/yts.php";
+		
+		$this->requests = array(
+			new LeetxRequest($opts, $mh), // 1337x
+			new NyaaRequest($opts, $mh),
+			new PirateBayRequest($opts, $mh),
+			new YTSRequest($opts, $mh)
+		);
+	}
+
+    public function parse_results($response) {
+        $results = $results_temp = array();
+
+        foreach ($this->requests as $request) {
+            if($request->request_successful()) {
+                $results_temp = array_merge($results_temp, $request->get_results());
+            }
+        }
+
+		if(count($results_temp) > 0) {
+			// Ensure highest seeders are shown first
+	        $seeders = array_column($results_temp, "seeders");
+	        array_multisort($seeders, SORT_DESC, $results_temp);
+	
+			// Cap results
+			$results['search'] = array_slice($results_temp, 0, 50);
+			unset($results_temp);
+		}
+
+		// Add warning if there are no results
+        if(empty($results)) {
+            $results["error"] = array(
+                "message" => "No results found. Please try with less or different keywords!" 
+            );
+        }
+
+        return $results; 
+    }
+
+    public static function print_results($results, $opts) {
+		if($opts->raw_output == "on") {
+			echo '<pre>Results: ';
+			print_r($results);
+			echo '</pre>';
+		}
+
+		echo "<section class=\"main-column\">";
+		echo "<ol>";
+
+		// Elapsed time
+		echo "<li class=\"meta-time\">Fetched the results in ".$results['time']." seconds.</li>";
+
+		// No results found
+        if(array_key_exists("error", $results)) {
+            echo "<li class=\"meta-error\">".$results['error']['message']."</li>";
+        }
+
+		// Search results
+		if(array_key_exists("search", $results)) {
+			foreach($results['search'] as $result) {
+				$meta = array();
+				// Optional data
+				if(array_key_exists('quality', $result)) $meta[] = "<strong>Quality:</strong> ".$result['quality'];
+				if(array_key_exists('year', $result)) $meta[] = "<strong>Year:</strong> ".$result['year'];
+				if(array_key_exists('category', $result)) $meta[] = "<strong>Category:</strong> ".$result['category'];
+				if(array_key_exists('runtime', $result)) $meta[] = "<strong>Runtime:</strong> ".date('H:i', mktime(0, $result['runtime']));
+				if(array_key_exists('date_added', $result)) $meta[] = "<strong>Added:</strong> ".date('M d, Y', $result['date_added']);
+				if(array_key_exists('url', $result)) $meta[] = "<a href=\"".$result["url"]."\" target=\"_blank\" title=\"Careful - Site may contain intrusive popup ads and malware!\">Torrent page</a>";
+	
+				// Put result together
+				echo "<li class=\"result\"><article>";
+				echo "<div class=\"url\"><a href=\"".$result["magnet"]."\" target=\"_blank\">".$result["source"]."</a></div>";
+				echo "<div class=\"title\"><a href=\"".$result["magnet"]."\" target=\"_blank\"><h2>".stripslashes($result["name"])."</h2></a></div>";
+				echo "<div class=\"description\"><strong>Seeds:</strong> <span class=\"seeders\">".$result['seeders']."</span> - <strong>Peers:</strong> <span class=\"leechers\">".$result['leechers']."</span> - <strong>Size:</strong> ".$result['size']."<br />".implode(" - ", $meta)."</div>";
+				echo "</article></li>";
+			}
+		}
+
+		echo "<li class=\"result\"><article>";
+		echo "<small>Goosle does not store, index, offer or distribute torrent files.</small>";
+		echo "</article></li>";
+
+		echo "</ol>";
+		echo "<center><small>Showing 50 results, sorted by most seeders.</small></center>";
+		echo "</section>";
+	}
+}
+?>

+ 164 - 0
engines/search.php

@@ -0,0 +1,164 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class TextSearch extends EngineRequest {
+    protected $engine, $engine_request, $special_request;
+
+    public function __construct($opts, $mh) {
+        $this->query = $opts->query;
+        $this->opts = $opts;
+
+        if($this->opts->type == 0) {            
+            require "engines/duckduckgo.php";
+            $this->engine_request = new DuckDuckGoRequest($opts, $mh);
+        }
+
+        if($this->opts->type == 1) {
+            require "engines/google.php";
+            $this->engine_request = new GoogleRequest($opts, $mh);
+        }
+
+		// Special search
+		$this->special_request = special_search_request($opts);
+    }
+
+    public function parse_results($response) {
+        $results = array();
+
+        // Abort if no results from search engine
+        if(!isset($this->engine_request)) return $results;
+
+		// Add search results
+		$success = $this->engine_request->request_successful();
+		if($success == "ok") {
+			$search_result = $this->engine_request->get_results();
+
+			if($search_result) {
+				$results['search'] = $search_result;
+			}
+			unset($search_result);
+		} else {
+            $results["error"] = array(
+                "message" => $success
+            );
+		}			
+
+		// Check for Special result
+        if($this->special_request) {
+            $special_result = $this->special_request->get_results();
+
+            if($special_result) {
+				$results['special'] = $special_result;
+            }
+			unset($special_result);
+        }
+
+		// Add warning if there are no results, or a text if there is no search query.
+		if(empty($results)) {
+			$results["error"] = array(
+				"message" => "No results found. Please try with less or different keywords!"
+			);
+		}
+
+        return $results;
+    }
+
+    public static function print_results($results, $opts)  {
+		if($opts->raw_output == "on") {
+			echo '<pre>Results: ';
+			print_r($results);
+			echo '</pre>';
+		}
+
+		echo "<section class=\"main-column\">";
+		echo "<ol>";
+
+		// Elapsed time
+		if(array_key_exists("search", $results)) {
+			$number_of_results = count($results['search']);
+			echo "<li class=\"meta-time\">Fetched ".$number_of_results." results in ".$results['time']." seconds.</li>";
+		}
+
+		// No results found
+        if(array_key_exists("error", $results)) {
+            echo "<li class=\"meta-error\">".$results['error']['message']."</li>";
+        }
+
+		// Did you mean/Search suggestion
+		if(array_key_exists("search", $results)) {
+			$specific_result = "";
+
+			if(array_key_exists("did_you_mean", $results['search'][0])) {
+				if(array_key_exists("search_specific", $results['search'][1])) {
+					// Add double quotes to Google search
+					$search_specific = ($opts->type == 1) ? "\"".$results['search'][1]['search_specific']."\"" : $results['search'][1]['search_specific'];
+					$search_specific_url = "./results.php?q="  . urlencode($search_specific)."&t=".$opts->type."&a=".$opts->hash;
+					$specific_result = "<br /><small>Or instead search for <a href=\"$search_specific_url\">$search_specific</a>.</small>";
+		
+					unset($results['search'][1], $search_specific, $search_specific_url);
+				}
+
+				$didyoumean = $results['search'][0]['did_you_mean'];
+				$didyoumean_url = "./results.php?q="  . urlencode($didyoumean)."&t=".$opts->type."&a=".$opts->hash;
+	
+				echo "<li class=\"meta-did-you-mean\">Did you mean <a href=\"$didyoumean_url\">$didyoumean</a>?$specific_result</li>";
+	
+				unset($results['search'][0], $didyoumean, $didyoumean_url, $specific_result);
+			}
+		}
+
+		// Special result
+		if(array_key_exists("special", $results)) {
+			echo "<li class=\"special-result\"><article>";
+			// Maybe shorten text
+			if(strlen($results['special']['text']) > 1250) {
+				$results['special']['text'] = substr($results['special']['text'], 0, strrpos(substr($results['special']['text'], 0, 1300), ". "));
+				$results['special']['text'] .= '. <a href="'.$results['special']['source'].'" target="_blank">[...]</a>';
+			}
+
+			// Add image to text
+			if(array_key_exists("image", $results['special'])) {
+				$image_specs = getimagesize($results['special']['image']);
+				$width = $image_specs[0] / 2;
+				$height = $image_specs[1] / 2;
+
+				$special_image = "<img src=\"".$results['special']['image']."\" align=\"right\" width=\"".$width."\" height=\"".$height."\" />";
+				$results['special']['text'] = $special_image.$results['special']['text'];
+
+				unset($image_specs, $width, $height, $special_image);
+			}
+			echo "<div class=\"title\"><h2>".$results['special']['title']."</h2></div>";
+			echo "<div class=\"text\">".$results['special']['text']."</div>";
+			if(array_key_exists("source", $results['special'])) {
+				echo "<div class=\"source\"><a href=\"".$results['special']['source']."\" target=\"_blank\">".$results['special']['source']."</a></div>";
+			}
+			echo "</article></li>";
+		}
+
+		// Search results
+		if(array_key_exists("search", $results)) {
+	        foreach($results['search'] as $result) {
+		        if(array_key_exists("did_you_mean", $result)) continue;
+	
+				// Put result together
+				echo "<li class=\"result\"><article>";
+				echo "<div class=\"url\"><a href=\"".$result["url"]."\" target=\"_blank\">".get_formatted_url($result["url"])."</a></div>";
+				echo "<div class=\"title\"><a href=\"".$result["url"]."\" target=\"_blank\"><h2>".$result["title"]."</h2></a></div>";
+				echo "<div class=\"description\">".$result["description"]."</div>";
+				echo "</article></li>";
+	        }
+		}
+ 
+        echo "</ol>";
+        echo "</section>";
+    }
+}
+?>

+ 55 - 0
engines/special/currency.php

@@ -0,0 +1,55 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class CurrencyRequest extends EngineRequest {
+    public function get_request_url() {
+        return "https://cdn.moneyconvert.net/api/latest.json";
+    }
+    
+    public function parse_results($response) {
+        $json_response = json_decode($response, true);
+
+		if(!empty($json_response)) {
+	        $result = $json_response["rates"];
+
+			// Process query
+			// [0] = AMOUNT
+			// [1] = FROM CURRENCY
+			// [2] = (to|in)
+			// [3] = TO CURRENCY
+			
+	        $query_terms = explode(" ", $this->query);
+	        $amount = floatval($query_terms[0]);
+	        $amount_currency = strtoupper($query_terms[1]);
+	        $conversion_currency = strtoupper($query_terms[3]);
+
+			// Unknown/misspelled currencies
+	        if (!array_key_exists($amount_currency, $result) || !array_key_exists($conversion_currency, $result)) {
+	            return array();
+			}
+	
+			// Calculate exchange rate
+	        $conversion = round(($result[$conversion_currency] / $result[$amount_currency]) * $amount, 4);
+
+	        return array(
+                "title" => "Currency conversion:",
+                "text" => "$amount $amount_currency = $conversion $conversion_currency",
+                "source" => "https://moneyconvert.net/"
+	        );
+	    } else {
+	        return array(
+                "title" => "Uh-oh...",
+                "text" => "No exchange rates could be loaded. Try again later."
+	        );
+		}
+    }
+}
+?>

+ 58 - 0
engines/special/definition.php

@@ -0,0 +1,58 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class DefinitionRequest extends EngineRequest {
+	public function get_request_url() {
+        $query_terms = explode(" ", $this->query);
+
+		// [0] = (define|d|mean|meaning)
+		// [1] = WORD
+
+		return "https://api.dictionaryapi.dev/api/v2/entries/en/".$query_terms[1];
+	}
+
+	public function parse_results($response) {
+		$json_response = json_decode($response, true);
+
+		if(!empty($json_response)) {
+			// Word not found
+			if (array_key_exists("title", $json_response)) {
+				return array(
+	                "title" => strip_tags(trim($json_response['title'])),
+	                "text" => strip_tags(trim($json_response['message']))
+				);
+			}
+	
+			// Grab first result if there are multiple
+			$result = $json_response[0];
+			$definitions = array_slice($result['meanings'][0]['definitions'], 0, 3);
+	
+			// Word found
+			$formatted_response = strip_tags(trim($result['meanings'][0]['partOfSpeech']))."<br /><ol class=\"word-definitions\">";
+			foreach($definitions as $key => $def) {
+				$formatted_response .= "<li>".strip_tags(trim($def['definition']))."</li>";
+			}
+			$formatted_response .= "</ol>";
+			
+			return array(
+				"title" => strip_tags(trim($result['word']))." <span>[".strip_tags(trim($result['phonetic']))."]</span>",
+				"text" => $formatted_response,
+				"source" => strip_tags(trim($result['sourceUrls'][0]))
+			);
+	    } else {
+	        return array(
+                "title" => "Whoops...",
+                "text" => "No definitions could be loaded. Try again later."
+	        );
+		}
+	}
+}
+?>

+ 47 - 0
engines/special/php.php

@@ -0,0 +1,47 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class PHPnetRequest extends EngineRequest {
+	public function get_request_url() {
+		$this->query = str_replace("_", "-", str_replace("php ", "", $this->query));
+
+		return "https://www.php.net/manual/function.".urlencode($this->query);
+	}
+	
+	public function parse_results($response) {
+        $results = array();
+        $xpath = get_xpath($response);
+
+        if($xpath) {
+			// Scrape the page
+			$title = $xpath->query("//div/section/div[@class='refentry']/div/h1[@class='refname']")[0]->textContent;
+			if(is_null($title)) return array();
+			$php_versions = $xpath->query("//div/section/div[@class='refentry']/div/p[@class='verinfo']")[0]->textContent;
+			$purpose = $xpath->query("//div/section/div[@class='refentry']/div/p[@class='refpurpose']")[0]->textContent;
+			$usage = $xpath->query("//div/section/div[@class='refentry']/div[@class='refsect1 description']/div[@class='methodsynopsis dc-description']")[0]->textContent;
+
+			$response = array (
+                // Required
+				"title" => sanitize($title),
+				"text" => "<p>".$purpose." <em><small>".$php_versions."</small></em></p><p>".highlight_string("<?php ".trim($usage)." ?>", 1)."</p>",
+				"source" => "https://www.php.net/manual/function.".urlencode($this->query)
+			);
+
+			return $response;
+	    } else {
+	        return array(
+                "title" => "Oof...",
+                "text" => "PHP.net didn't provide any answers. Try again later."
+	        );
+		}
+	}
+}
+?>

+ 66 - 0
engines/special/wikipedia.php

@@ -0,0 +1,66 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class WikipediaRequest extends EngineRequest {
+	public function get_request_url() {
+        $query_terms = explode(" ", $this->query);
+ 
+ 		// [0] = (wiki|w)
+		// [1] = SEARCH TERM
+
+		unset($query_terms[0]); // Remove first item (w or wiki) from array and encode the rest for Wikipedia	
+		$this->query = implode(" ", $query_terms);
+	
+		return "https://wikipedia.org/w/api.php?format=json&action=query&prop=extracts%7Cpageimages&exintro&explaintext&redirects=1&pithumbsize=500&titles=".urlencode($this->query);
+	}
+	
+	public function parse_results($response) {
+		$json_response = json_decode($response, true);
+
+		if(!empty($json_response)) {
+			$result = $json_response['query']['pages'];
+			
+			// Abort on invalid response
+			if (!is_array($result)) return array();
+	
+			// Grab first result if there are multiple
+			$result = $result[array_key_first($result)];
+
+			// Page not found
+			if (array_key_exists("missing", $result)) {
+				return array(
+					"title" => "Wiki page not found", 
+					"text" => "Maybe the page doesn't exist. Try searching on Wikipedia with the link below or search for something else.", 
+					"source" => "https://wikipedia.org/wiki/Special:Search?go=Go&search=".urlencode($this->query)
+				);
+			}
+
+			// Page found
+			$response = array(
+				"title" => strip_tags(trim($result['title'])),
+				"text" => strip_tags(trim($result['extract'])),
+				"source" => "https://wikipedia.org/wiki/".urlencode($this->query)
+			);
+			
+			if (array_key_exists("thumbnail",  $result)) {
+				$response["image"] = strip_tags(trim($result["thumbnail"]["source"]));
+			}
+			
+			return $response;
+	    } else {
+	        return array(
+                "title" => "Sigh...",
+                "text" => "Wikipedia could not be loaded. Try again later."
+	        );
+		}
+	}
+}
+?>

+ 142 - 0
engines/torrent/1337x.php

@@ -0,0 +1,142 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class LeetxRequest extends EngineRequest {
+    public function get_request_url() {
+        return "https://1337x.to/search/".urlencode($this->query)."/1/";
+    }
+
+    public function parse_results($response) {
+        $results = array();
+        $xpath = get_xpath($response);
+
+		// Failted to load page
+        if(!$xpath) return $results;
+
+		$categories = array(
+			1 => "DVD",
+			2 => "Divx/Xvid",
+			3 => "SVCD/VCD",
+			4 => "Dubs/Dual Audio",
+			5 => "DVD",
+			6 => "Divx/Xvid",
+			7 => "SVCD/VCD",
+			9 => "Documentary",
+
+			10 => "PC Game",
+			11 => "PS2",
+			12 => "PSP",
+			13 => "Xbox",
+			14 => "Xbox360",
+			15 => "PS1",
+			16 => "Dreamcast",
+			17 => "Other (Gaming)",
+			18 => "PC Software",
+			19 => "Mac Software",
+
+			20 => "Linux Software",
+			21 => "Other (Software)",
+			22 => "MP3",
+			23 => "Lossless Audio",
+			24 => "DVD (Music)",
+			25 => "Music Video",
+			26 => "Radio",
+			27 => "Other (Audio)",
+			28 => "Anime",
+
+			33 => "Emulation",
+			34 => "Tutorials",
+			35 => "Sounds",
+			36 => "E-Books",
+			37 => "Images",
+			38 => "Mobile Phone",
+			39 => "Comics",
+
+			40 => "Other",
+			41 => "HD (Video)",
+			42 => "HD (Video)",
+			43 => "PS3",
+			44 => "Wii",
+			45 => "DS",
+			46 => "GameCube",
+			47 => "Nulled Script",
+			48 => "Video",
+			49 => "Picture",
+
+			50 => "Magazine",
+			51 => "Hentai",
+			52 => "Audiobook",
+			53 => "Album (Music)",
+			54 => "h.264/x264",
+			55 => "Mp4",
+			56 => "Android",
+			57 => "iOS",
+			58 => "Box Set (Music)",
+			59 => "Discography",
+
+			60 => "Single (Music)",
+			66 => "3D",
+			67 => "Games",
+			68 => "Concerts",
+			69 => "AAC (Music)",
+
+			70 => "HEVC/x265",
+			71 => "HEVC/x265",
+			72 => "3DS",
+			73 => "Bollywood",
+			74 => "Cartoon",
+			75 => "SD (Video)",
+			76 => "UHD",
+			77 => "PS4",
+			78 => "Dual Audio (Video)",
+			79 => "Dubbed (Video)",
+
+			80 => "Subbed",
+			81 => "Raw",
+			82 => "Switch",
+		);
+
+		// Scrape the page
+        foreach($xpath->query("//table/tbody/tr") as $result) {
+			$category = $xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[0]->textContent;
+			$category = explode("/", trim($category));
+
+            // Block these categories
+            if(in_array($category[2], $this->opts->leetx_categories_blocked)) continue;
+
+			$name = $xpath->evaluate(".//td[@class='coll-1 name']/a", $result)[1]->textContent;
+			$seeders = $xpath->evaluate(".//td[@class='coll-2 seeds']", $result)[0]->textContent;
+			$leechers = $xpath->evaluate(".//td[@class='coll-3 leeches']", $result)[0]->textContent;
+			$date_added = explode(" ", sanitize($xpath->evaluate(".//td[@class='coll-date']", $result)[0]->textContent));
+			$date_added = mktime(0, 0, 0, date("m", strtotime($date_added[0])), preg_replace('/[^\d.]+/', '', $date_added[1]), intval('20'.preg_replace('/[^\d.]+/', '', $date_added[2])));
+			$url = "https://1337x.to".sanitize($xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[1]->textContent);
+			$size_unformatted = explode(" ", $xpath->evaluate(".//td[contains(@class, 'coll-4 size')]", $result)[0]->textContent);
+			$size = $size_unformatted[0] . " " . preg_replace("/[0-9]+/", "", $size_unformatted[1]);
+			
+			array_push($results, array (
+                // Required
+				"source" => "1337x.to",
+				"name" => sanitize($name),
+				"magnet" => "./engines/torrent/magnetize_1337x.php?url=".$url,
+				"seeders" => sanitize($seeders),
+				"leechers" => sanitize($leechers),
+				"size" => sanitize($size),
+				// Optional values
+				"category" => $categories[sanitize($category[2])],
+				"url" => $url,
+				"date_added" => $date_added
+			));
+        }
+
+        return $results;
+    }
+}
+?>

+ 49 - 0
engines/torrent/magnetize_1337x.php

@@ -0,0 +1,49 @@
+<?php
+require "../../misc/tools.php";
+$opts = require "../../config.php";
+
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+
+$ch = curl_init();
+
+curl_setopt($this->ch, CURLOPT_URL, $_REQUEST["url"]);
+curl_setopt($this->ch, CURLOPT_HTTPGET, 1); // Redundant? Probably...
+curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
+curl_setopt($this->ch, CURLOPT_VERBOSE, false);
+curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
+curl_setopt($this->ch, CURLOPT_USERAGENT, $opts->user_agents[array_rand($opts->user_agents)]);
+curl_setopt($this->ch, CURLOPT_HTTPHEADER, array(
+    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+    'Accept-Language: en-US,en;q=0.5',
+    'Upgrade-Insecure-Requests: 1'
+));
+curl_setopt($this->ch, CURLOPT_ENCODING, "gzip,deflate");
+curl_setopt($this->ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
+curl_setopt($this->ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+curl_setopt($this->ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+curl_setopt($this->ch, CURLOPT_MAXREDIRS, 5);
+curl_setopt($this->ch, CURLOPT_TIMEOUT, 3);
+curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
+
+$response = curl_exec($ch);
+curl_close($ch);
+
+$xpath = get_xpath($response);
+
+// No results
+if(!$xpath) die();
+
+$magnet = $xpath->query("//main/div/div/div/div/div/ul/li/a/@href")[0]->textContent;
+$magnet = trim($magnet);
+
+header("Location: $magnet")
+?>

+ 56 - 0
engines/torrent/nyaa.php

@@ -0,0 +1,56 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class NyaaRequest extends EngineRequest {
+    public function get_request_url() {
+        return "https://nyaa.si/?q=".urlencode($this->query);
+    }
+
+    public function parse_results($response) {
+        $results = array();
+        $xpath = get_xpath($response);
+
+		// Failted to load page
+        if(!$xpath) return $results;
+
+		// Scrape the page
+        foreach($xpath->query("//tbody/tr") as $result) {
+ 			$category = $xpath->evaluate(".//td[1]//a/@title", $result)[0]->textContent;
+            $name = $xpath->evaluate(".//td[@colspan='2']//a[not(contains(@class, 'comments'))]/@title", $result)[0]->textContent;
+            $url = $xpath->evaluate(".//td[@colspan='2']//a[not(contains(@class, 'comments'))]/@href", $result)[0]->textContent;
+            $meta = $xpath->evaluate(".//td[@class='text-center']", $result);
+            $seeders = $meta[3]->textContent;
+            $leechers = $meta[4]->textContent;
+            $size =  $meta[1]->textContent;
+            $date_added =  $meta[2]->textContent;
+            $date_added = explode("-", substr(sanitize($date_added), 0, 10));
+			$date_added = mktime(0, 0, 0, $date_added[1], $date_added[2], $date_added[0]);
+            $magnet = $xpath->evaluate(".//a[2]/@href", $meta[0])[0]->textContent;
+
+            array_push($results, array (
+                // Required
+                "source" => "nyaa.si",
+                "name" => sanitize($name),
+                "magnet" => sanitize($magnet),
+                "seeders" => sanitize($seeders),
+                "leechers" => sanitize($leechers),
+                "size" => sanitize($size),
+				// Optional values
+				"category" => str_replace(" - ", "/", sanitize($category)),
+                "url" => "https://nyaa.si".sanitize($url),
+				"date_added" => $date_added
+            ));
+        }
+
+        return $results;
+    }
+}
+?>

+ 115 - 0
engines/torrent/thepiratebay.php

@@ -0,0 +1,115 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class PirateBayRequest extends EngineRequest {
+    public function get_request_url() {
+        return "https://apibay.org/q.php?q=".urlencode($this->query);
+    }
+
+    public function parse_results($response) {
+        $results = array();
+        $json_response = json_decode($response, true);
+
+		// No response
+        if(empty($json_response)) return $results;
+
+		$categories = array(
+			100 => "Audio",
+			101 => "Music",
+			102 => "Audio Book",
+			103 => "Sound Clips",
+			104 => "Audio FLAC",
+			199 => "Audio Other",
+
+			200 => "Video",
+			201 => "Movie",
+			202 => "Movie DVDr",
+			203 => "Music Video",
+			204 => "Movie Clip",
+			205 => "TV Show",
+			206 => "Handheld",
+			207 => "HD Movie",
+			208 => "HD TV Show",
+			209 => "3D Movie",
+			210 => "CAM/TS",
+			211 => "UHD/4K Movie",
+			212 => "UHD/4K TV Show",
+			299 => "Video Other",
+			
+			300 => "Applications",
+			301 => "Apps Windows",
+			302 => "Apps Apple",
+			303 => "Apps Unix",
+			304 => "Apps Handheld",
+			305 => "Apps iOS",
+			306 => "Apps Android",
+			399 => "Apps Other OS",
+
+			400 => "Games",
+			401 => "Games PC",
+			402 => "Games Apple",
+			403 => "Games PSx",
+			404 => "Games XBOX360",
+			405 => "Games Wii",
+			406 => "Games Handheld",
+			407 => "Games iOS",
+			408 => "Games Android",
+			499 => "Games Other OS",
+			
+			500 => "Porn",
+			501 => "Porn Movie",
+			502 => "Porn Movie DVDr",
+			503 => "Porn Pictures",
+			504 => "Porn Games",
+			505 => "Porn HD Movie",
+			506 => "Porn Movie Clip",
+			507 => "Porn UHD/4K Movie",
+			599 => "Porn Other",
+
+			600 => "Other",
+			601 => "Other E-Book",
+			602 => "Other Comic",
+			603 => "Other Pictures",
+			604 => "Other Covers",
+			605 => "Other Physibles",
+			699 => "Other Other"
+		);
+
+		// Use API result
+        foreach($json_response as $response) {
+			// Nothing found
+            if($response["name"] == "No results returned") break;
+
+            // Block these categories
+            if(in_array($response["category"], $this->opts->piratebay_categories_blocked)) continue;
+
+            $name = sanitize($response["name"]);
+            $magnet = "magnet:?xt=urn:btih:".sanitize($response["info_hash"])."&dn=".$name."&tr=".implode("&tr=", $this->opts->torrent_trackers);
+
+            array_push($results, array (
+                // Required
+                "source" => "thepiratebay.org",
+                "name" => $name,
+                "magnet" => $magnet,
+				"seeders" => sanitize($response["seeders"]),
+                "leechers" => sanitize($response["leechers"]),
+                "size" => human_filesize(sanitize($response["size"])),
+				// Optional
+				"category" => $categories[sanitize($response["category"])],
+                "url" => "https://thepiratebay.org/description.php?id=".sanitize($response["id"]),
+ 				"date_added" => sanitize($response["added"]),
+           ));
+        }
+
+        return $results;
+    }
+}
+?>

+ 66 - 0
engines/torrent/yts.php

@@ -0,0 +1,66 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+class YTSRequest extends EngineRequest {
+    public function get_request_url() {
+        return "https://yts.mx/api/v2/list_movies.json?query_term=".urlencode($this->query);
+    }
+
+    public function parse_results($response) {
+        $results = array();
+        $response = curl_multi_getcontent($this->ch);
+        $json_response = json_decode($response, true);
+
+		// No response
+        if(empty($json_response)) return $results;
+
+		// Nothing found
+        if($json_response["status"] != "ok" || $json_response["data"]["movie_count"] == 0) return $results;
+
+		// Use API result
+        foreach ($json_response["data"]["movies"] as $movie) {
+			// Prevent gaps
+            if(!array_key_exists("year", $movie)) $movie['year'] = 0;
+            if(!array_key_exists("genres", $movie)) $movie['genres'] = array();
+            if(!array_key_exists("runtime", $movie)) $movie['runtime'] = 0;
+            if(!array_key_exists("url", $movie)) $movie['url'] = '';
+
+            // Block these categories
+           	if(array_intersect($movie["genres"], $this->opts->yts_categories_blocked)) continue;
+
+            $name = sanitize($movie["title"]);
+
+            foreach ($movie["torrents"] as $torrent) {
+                $magnet = "magnet:?xt=urn:btih:".sanitize($torrent["hash"])."&dn=".urlencode($name)."&tr=".implode("&tr=", $this->opts->torrent_trackers);
+
+                array_push($results, array (
+	                // Required
+					"source" => "yts.mx",
+					"name" => $name,
+					"magnet" => $magnet,
+					"seeders" => sanitize($torrent["seeds"]),
+					"leechers" => sanitize($torrent["peers"]),
+					"size" => sanitize($torrent["size"]),
+					// Optional
+					"quality" => sanitize($torrent["quality"]),
+					"year" => sanitize($movie["year"]),
+					"category" => sanitize(implode(', ', $movie["genres"])),
+					"runtime" => sanitize($movie["runtime"]),
+					"url" => sanitize($movie["url"]),
+					"date_added" => sanitize($movie["date_uploaded_unix"])
+				));
+            }
+        }
+
+		return $results;
+	}
+}
+?>

BIN
favicon.ico


+ 188 - 0
functions/search_engine.php

@@ -0,0 +1,188 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+abstract class EngineRequest {
+    protected $url, $query, $opts, $mh, $ch;
+
+	function __construct($opts, $mh) {
+		$this->query = $opts->query;
+		$this->mh = $mh;
+		// Must be in this order :-/
+		$this->opts = $opts;
+		$this->url = $this->get_request_url();
+
+		// No search engine url
+		if(!$this->url) return;
+		
+		// Skip if there is a cached result (from earlier search)
+		if($this->opts->cache == "on" && has_cached_results($this->url, $this->opts->hash)) return;
+		
+		// Curl
+		$this->ch = curl_init();
+
+		curl_setopt($this->ch, CURLOPT_URL, $this->url);
+		curl_setopt($this->ch, CURLOPT_HTTPGET, 1); // Redundant? Probably...
+		curl_setopt($this->ch, CURLOPT_SSL_VERIFYPEER, false);
+		curl_setopt($this->ch, CURLOPT_VERBOSE, false);
+		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
+		curl_setopt($this->ch, CURLOPT_USERAGENT, $this->opts->user_agents[array_rand($this->opts->user_agents)]);
+		curl_setopt($this->ch, CURLOPT_HTTPHEADER, array(
+		    'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
+		    'Accept-Language: en-US,en;q=0.5',
+		    'Upgrade-Insecure-Requests: 1'
+		));
+		curl_setopt($this->ch, CURLOPT_ENCODING, "gzip,deflate");
+		curl_setopt($this->ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
+		curl_setopt($this->ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+		curl_setopt($this->ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS | CURLPROTO_HTTP);
+		curl_setopt($this->ch, CURLOPT_MAXREDIRS, 5);
+		curl_setopt($this->ch, CURLOPT_TIMEOUT, 3);
+		curl_setopt($this->ch, CURLOPT_FOLLOWLOCATION, true);
+		
+		if($mh) curl_multi_add_handle($mh, $this->ch);
+	}
+
+	/*--------------------------------------
+	// Get search engine url
+	--------------------------------------*/
+	public function get_request_url() {
+		return "";
+	}
+	
+	/*--------------------------------------
+	// Check if a request to a search engine was successful
+	--------------------------------------*/
+	public function request_successful() {
+		if((isset($this->ch) && curl_getinfo($this->ch)['http_code'] == '200') || has_cached_results($this->url, $this->opts->hash)) {
+			$return = "ok";
+		} else {
+            $return = "Error code ".curl_getinfo($this->ch)['http_code']." for ".curl_getinfo($this->ch)['url'].". <a href=\"".curl_getinfo($this->ch)['url']."\" target=\"_blank\">Go there now</a>.";
+		}			
+
+		return $return;
+	}
+	
+	abstract function parse_results($response);
+
+	/*--------------------------------------
+	// Load search results
+	--------------------------------------*/
+	public function get_results() {
+
+		if(!isset($this->url)) return $this->parse_results(null);
+
+		// Skip if there is a cached result (from earlier search)
+		if($this->opts->cache == "on" && has_cached_results($this->url, $this->opts->hash)) return fetch_cached_results($this->url, $this->opts->hash);
+	
+		if(!isset($this->ch)) return $this->parse_results(null);
+		$response = ($this->mh) ? curl_multi_getcontent($this->ch) : curl_exec($this->ch);
+
+		$results = $this->parse_results($response) ?? array();
+	
+		// Cache last request
+		if($this->opts->cache == "on" && !empty($results)) store_cached_results($this->url, $this->opts->hash, $results, ($this->opts->cache_time * 60));
+
+		return $results;
+	}
+	
+	public static function print_results($results, $opts) {}
+}
+
+/*--------------------------------------
+// Load and make config available, pass around variables
+--------------------------------------*/
+function load_opts() {
+    $opts = require "config.php";
+
+    $opts->query = (isset($_REQUEST['q'])) ? sanitize($_REQUEST["q"]) : "";
+    $opts->type = (isset($_REQUEST['t'])) ? sanitize($_REQUEST["t"]) : 0;
+    $opts->user_auth = (isset($_REQUEST['a'])) ? sanitize($_REQUEST["a"]) : "";
+
+	// Remove ! at the start of queries to prevent DDG Bangs (!g, !c and crap like that)
+	$has_exclamation_mark = substr($opts->query, 0, 1);
+	if($has_exclamation_mark == "!") $opts->query = ltrim($opts->query, "!");
+
+    return $opts;
+}
+
+/*--------------------------------------
+// Try to get some search results
+--------------------------------------*/
+function fetch_search_results($opts) {
+    $start_time = microtime(true);
+
+	// Curl
+    $mh = curl_multi_init();
+
+	// Load search script
+    if($opts->type == 0 || $opts->type == 1) {
+        require "engines/search.php";
+        $search = new TextSearch($opts, $mh);
+	} else if($opts->type == 2) {
+	    require "engines/search-image.php";
+        $search = new ImageSearch($opts, $mh);
+	} else if($opts->type == 9) {
+	    require "engines/search-torrent.php";
+        $search = new TorrentSearch($opts, $mh);
+    }
+
+    $running = null;
+
+    do {
+        curl_multi_exec($mh, $running);
+    } while ($running);
+
+    $results = $search->get_results();
+
+	curl_multi_close($mh);
+
+	// Add elapsed time to results
+	$results['time'] = number_format(microtime(true) - $start_time, 5, '.', '');
+
+    $search->print_results($results, $opts);
+
+    return $results;
+}
+
+/*--------------------------------------
+// Process special searches
+--------------------------------------*/
+function special_search_request($opts) {
+	$special_request = null;
+    $query_terms = explode(" ", $opts->query);
+
+	// Currency converter
+	if($opts->special['currency'] == "on" && is_numeric($query_terms[0]) && ($query_terms[2] == 'to' || $query_terms[2] == 'in')) {
+        require "engines/special/currency.php";
+        $special_request = new CurrencyRequest($opts, null);
+	}
+	
+	// Dictionary
+	if($opts->special['definition'] == "on" && count($query_terms) == 2 && ($query_terms[0] == 'define' || $query_terms[0] == 'd' || $query_terms[0] == 'mean' || $query_terms[0] == 'meaning')) {
+        require "engines/special/definition.php";
+        $special_request = new DefinitionRequest($opts, null);
+	}
+
+	// Wikipedia search
+	if($opts->special['wikipedia'] == "on" && count($query_terms) >= 2 && ($query_terms[0] == 'wiki' || $query_terms[0] == 'w')) {
+        require "engines/special/wikipedia.php";
+        $special_request = new WikipediaRequest($opts, null);
+	}
+
+	// php.net search
+	if($opts->special['phpnet'] == "on" && count($query_terms) == 2 && $query_terms[0] == 'php') {
+        require "engines/special/php.php";
+        $special_request = new PHPnetRequest($opts, null);
+	}
+	
+	return $special_request;
+}
+?>

+ 127 - 0
functions/tools.php

@@ -0,0 +1,127 @@
+<?php
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+
+/*--------------------------------------
+// Verify the hash, or not, and let people in, or not
+--------------------------------------*/
+function verify_hash($opts, $auth) {
+	if(($opts->hash_auth == "on" && strtolower($opts->hash) === strtolower($auth)) || $opts->hash_auth == "off") return true;
+
+    return false;
+}
+
+/*--------------------------------------
+// Strip all extras from an url
+--------------------------------------*/
+function get_base_url($url) {
+    $parsed = parse_url($url);
+
+    return $parsed["scheme"] . "://" . $parsed["host"] . "/";
+}
+
+/*--------------------------------------
+// Format search result urls
+--------------------------------------*/
+function get_formatted_url($url) {
+    $parsed = parse_url($url);
+
+    return $parsed["scheme"] . "://" . $parsed["host"] . str_replace('/', ' &rsaquo; ', str_replace('%20', ' ', rtrim($parsed['path'], '/')));
+}
+
+/*--------------------------------------
+// Load pages into a DOM
+--------------------------------------*/
+function get_xpath($response) {
+    if(!$response)
+        return null;
+
+    $htmlDom = new DOMDocument;
+    @$htmlDom->loadHTML($response);
+    $xpath = new DOMXPath($htmlDom);
+
+    return $xpath;
+}
+
+/*--------------------------------------
+// APCu Caching
+--------------------------------------*/
+function has_cached_results($url, $hash) {
+	if(function_exists("apcu_exists")) {
+		return apcu_exists("$hash:$url");
+	}
+
+	return false;
+}
+
+function store_cached_results($url, $hash, $results, $ttl = 0) {
+	if(function_exists("apcu_store") && !empty($results)) {
+		return apcu_store("$hash:$url", $results, $ttl);
+	}
+}
+
+function fetch_cached_results($url, $hash) {
+	if(function_exists("apcu_fetch")) {
+		return apcu_fetch("$hash:$url");
+	}
+	
+	return array();
+}
+
+/*--------------------------------------
+// Sanitize variables
+--------------------------------------*/
+function sanitize($thing) {
+	switch(gettype($thing)) {
+		case 'string': 
+			$thing = stripslashes(strip_tags(trim($thing)));
+		break;
+		case 'boolean':
+			$thing = ($thing === FALSE) ? 0 : 1;
+		break;
+		default: 
+			$thing = ($thing === NULL) ? 'NULL' : strip_tags(trim($thing));
+		break;
+	}
+
+    return $thing;
+}
+
+/*--------------------------------------
+// Human readable file sizes
+--------------------------------------*/
+function human_filesize($bytes, $dec = 2) {
+    $size   = array('B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
+    $factor = floor((strlen($bytes) - 1) / 3);
+
+    return sprintf("%.{$dec}f ", $bytes / pow(1024, $factor)) . @$size[$factor];
+}
+
+/*--------------------------------------
+// Generate random strings for passwords
+--------------------------------------*/
+function string_generator() {
+    $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
+    $password = array();
+    $length = strlen($characters) - 1;
+
+    for ($i = 0; $i < 24; $i++) {
+        $n = rand(0, $length);
+        $password[] = $characters[$n];
+    }
+
+    array_splice($password, 6, 0, '-');
+	array_splice($password, 13, 0, '-');
+	array_splice($password, 20, 0, '-');
+
+    return implode($password);
+}
+?>

+ 44 - 0
goosle.htaccess

@@ -0,0 +1,44 @@
+# mod_rewrite
+RewriteEngine on
+
+# FORCE SSL
+RewriteCond %{HTTPS} !=on
+RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
+
+# Redirect 404 to homepage
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteCond %{REQUEST_FILENAME} !-d
+RewriteRule . / [L,R=301]
+
+# Use UTF-8 encoding for anything served text/plain or text/html
+AddDefaultCharset UTF-8
+AddCharset UTF-8 .css .js
+
+# Cache Control
+ExpiresByType image/webp "access plus 1 year"
+ExpiresByType image/x-icon "access plus 1 year"
+ExpiresByType font/woff "access plus 1 year"
+ExpiresByType font/woff2 "access plus 1 year"
+ExpiresByType application/font-woff "access plus 1 year"
+ExpiresByType text/css "access plus 1 year"
+ExpiresByType text/javascript "access plus 1 year"
+ExpiresByType application/javascript "access plus 1 year"
+# Failsaves
+ExpiresByType application/octet-stream "access plus 1 year"
+ExpiresByType text/plain "access plus 1 year"
+ExpiresDefault "access 30 days"
+
+# Gzip compression
+SetOutputFilter DEFLATE
+
+# Don’t compress images and other uncompressible content
+SetEnvIfNoCase Request_URI \
+\.(?:gif|jpe?g|png|rar|zip|exe|flv|mov|wma|mp3|avi|swf|mp?g|mp4|webm|webp)$ no-gzip dont-vary
+
+# Compress all output labeled with one of the following MIME-types
+AddOutputFilterByType DEFLATE application/atom+xml application/javascript application/json application/rss+xml application/vnd.ms-fontobject application/x-font-ttf application/xhtml+xml application/xml font/ttf font/otf font/opentype image/svg+xml image/x-icon text/css text/html text/plain text/x-component text/xml
+Header append Vary: Accept-Encoding
+
+# Force deflate for mangled headers
+SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
+RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding

+ 129 - 0
help.php

@@ -0,0 +1,129 @@
+<?php
+require "functions/tools.php";
+require "functions/search_engine.php";
+
+$opts = load_opts();
+$auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+?>
+<!DOCTYPE html >
+<html lang="en">
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <meta charset="UTF-8"/>
+    <meta name="description" content="A private meta search engine!"/>
+    <meta name="referrer" content="no-referrer"/>
+	<link rel="apple-touch-icon" href="apple-touch-icon.png">
+	<link rel="icon" href="favicon.ico" type="image/x-icon"/>
+    <link rel="stylesheet" type="text/css" href="assets/css/styles.css"/>
+	<title><?php echo $opts->query; ?> - Goosle Search</title>
+</head>
+<body>
+<?php
+if(verify_hash($opts, $auth)) {
+?>
+<div class="wrap">
+	<div class="header-wrap">
+		<form action="results.php" method="get" autocomplete="off">
+		    <h1 class="logo"><a class="no-decoration" href="./?a=<?php echo $opts->hash; ?>"><span class="G">G</span>oosle</a></h1>        
+		    <input tabindex="1" class="search" type="text" value="<?php echo (strlen($opts->query) > 0) ? htmlspecialchars($opts->query) : "" ; ?>" name="q" required /><input tabindex="2" class="button" type="submit" value="Search" />
+		
+	        <input type="hidden" name="t" value="<?php echo $opts->type; ?>"/>
+		    <input type="hidden" name="a" value="<?php echo $opts->hash; ?>">
+	 
+	        <div class="navigation-header">
+		        <a <?php echo ($opts->type == "0") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=0"><img src="assets/images/search.png" alt="DuckDuckGo results" />DuckDuckGo</a>
+		        <a <?php echo ($opts->type == "1") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=1"><img src="assets/images/search.png" alt="Google results" />Google</a>
+		        <?php if($opts->enable_image_search == "on") { ?>
+		        <a <?php echo ($opts->type == "2") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=2"><img src="assets/images/image.png" alt="Image results" />Image</a>
+		        <?php } ?>
+		        <?php if($opts->enable_torrent_search == "on") { ?>
+		        <a <?php echo ($opts->type == "9") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=9"><img src="assets/images/torrent.png" alt="Torrent results" />Torrent</a>
+		        <?php } ?>
+			</div>
+		</form>
+	</div>
+	
+	<div class="results-wrap">
+		<section class="main-column">
+			<h2>DuckDuckGo features</h2>
+			<p>DuckDuckGo is mostly language agnostic and will try to figure out on it's own what language to use.</p>
+			<p>Searching defaults to Moderate Safe mode. To override the safe mode prefix your search with <strong>safe:on</strong> or <strong>safe:off</strong>.<br /><strong>On</strong> will use 'Strict' mode, while <strong>off</strong> will disable safe searching, this may yield results that are unsuitable for work or minors.</p>
+			<p><em><strong>Note:</strong> Search bangs are not supported and the <strong>!</strong> to trigger them is stripped out to prevent issues.</em></p>
+			
+			<h2>Google features</h2>
+			<p>Searching defaults to Moderate Safe mode. You can override safe mode by adding the prefix <strong>safe:on</strong> or <strong>safe:off</strong> to your search query.<br /><strong>On</strong> will use 'Strict' mode, while <strong>off</strong> will disable safe searching. this may yield results that are unsuitable for work or minors.</p>
+			<p>Google results are not personalized by default, using Google's option for it.</p>
+			<p>Google search is language agnostic and Google will try to figure out on it's own what language to use. To search in a specific language prefix your search with <strong>lang fr</strong> for French or <strong>lang:es</strong> for Spanish. Any language that google supports will work as long as you use the ISO639-1:2002 language code. Commonly these are the 2 letter abbreviations for the language such as; en, fr, es, de, sk, and so on.</p>
+			<p>To do a category search, prefix your search with one of the following keywords; <strong>app</strong>, <strong>book</strong>, <strong>news</strong>, <strong>shop</strong> or <strong>patent</strong>. This will tell Google to look for results in that specific category.<br />For example: Searching for <strong>book trainspotting</strong> will (or should) show results related to the book Trainspotting.</p>
+
+			<?php if($opts->enable_image_search == "on") { ?>
+				<h2>Image Search</h2>
+				<p>Search for images through Qwant Image Search.<br />The number of results is limited to 50.</p>
+				<p>Contrary to Google Image Search which opens a popup/slider with more information. Goosle Image Search links directly to the page where the image is hosted.</p>
+				<p>You can search for images in a general size by adding <strong>size:small</strong>, <strong>size:medium</strong> or <strong>size:large</strong> to your search query.</p>
+			<?php } ?>
+			
+			<h2>Special Searches</h2>
+			<?php if($opts->special['currency'] == "on") { ?>
+				<h3>Currency converter</h3>
+				<p>Convert currency with a simple query.<br />
+				For example: Search for <strong>20 EUR in HKD</strong> or <strong>20 USD to MXN</strong> and DuckDuckGo or Google will search for it, but also a local conversion is done in a highlighted result.</p>
+			<?php } ?>
+			
+			<?php if($opts->special['wikipedia'] == "on") { ?>
+				<h3>Wikipedia Search</h3>
+				<p>Prefix your search with <strong>w</strong> or <strong>wiki</strong> to search on Wikipedia for a page match. This works best for English searches as Wikipedia defaults to English.<br />
+				For example: Searching for <strong>wiki beach ball</strong> will show you a excerpt from that page above the search results or suggest the most likely alternative if Wikipedia knows what your search query means.</p>
+			<?php } ?>
+			
+			<?php if($opts->special['phpnet'] == "on") { ?>
+				<h3>PHP.net Search</h3>
+				<p>Prefix your search with <strong>php</strong> to search on php.net for a PHP function.<br />
+				For example: Searching for <strong>php in_array</strong> or <strong>php trim</strong> will show you a brief description, compatible PHP versions and a usage example for that function.</p>
+			<?php } ?>
+			
+			<?php if($opts->special['definition'] == "on") { ?>
+				<h3>Word Definition</h3>
+				<p>You can easily look up the meaning of single words. Prefix the word you want to look up with any of the following keywords; <strong>d</strong>, <strong>define</strong>, <strong>mean</strong>, <strong>meaning</strong>.<br />
+				For example: Searching for <strong>define search</strong> will search for that on Google or DuckDuckGo, but also show the definition highlighted above the search results.</p>
+				<p><em><strong>Note:</strong> Special Searches do not work for torrent searches.</em></p>
+			<?php } ?>
+			
+			<?php if($opts->enable_torrent_search == "on") { ?>
+				<h2>Torrent Search</h2>
+				<p>Search for anything torrent sites have on offer the direct search result should give you the magnet link.<br />Results are gathered from 1337x, Nyaa, The Pirate Bay and YTS and are sorted by Seeders, highest first. The number of results is limited to 50.</p>
+				<p>The search scripts will try to provide useful data which may include; Seeders/Leechers, A link to the torrent page, Download Category, Release year, Movie quality (720p, 4K etc.), Movie Runtime and the Download Size. Not every website makes this available and all results take a best effort approach.</p>
+				<p><em><strong>Disclaimer:</strong> If you like, or found a use for, what you downloaded, you should probably buy a legal copy of it.</em></p>
+			<?php } ?>
+
+			<p><small><strong>Acknowledgements:</strong><br />Goosle started as a fork of LibreY, and takes some design cues from DuckDuckGo.com. Goosle is created by <a href="https://ajdg.solutions/" target="_blank">Arnan de Gans</a>.</small></p>
+
+			<p><small><strong>Version:</strong> <?php echo $opts->version; ?> / <strong>Released:</strong> <?php echo $opts->released; ?></small></p>
+		</section>
+	</div>
+</div>
+
+<div class="footer-wrap">
+	<div class="footer">
+		&copy; <?php echo date('Y'); ?> <a href="https://ajdg.solutions/" target="_blank">Arnan de Gans</a>. All rights reserved.
+		<span style="float:right;"><a href="./?a=<?php echo $opts->hash; ?>">Start</a> - <a href="./help.php?a=<?php echo $opts->hash; ?>">Help</a> - Your IP: <?php echo $_SERVER["REMOTE_ADDR"]; ?></span>
+	</div>
+</div>
+
+<?php 
+} else {
+	echo "<div class=\"auth-error\">Nope, go away!</div>";
+} 
+?>
+</body>
+</html>

+ 72 - 0
index.php

@@ -0,0 +1,72 @@
+<?php 
+require "functions/tools.php";
+require "functions/search_engine.php";
+
+$opts = load_opts();
+$auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+?>
+<!DOCTYPE html >
+<html lang="en">
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <meta charset="UTF-8"/>
+    <meta name="description" content="Goosle - A meta search engine for private and fast internet fun!"/>
+    <meta name="referrer" content="no-referrer"/>
+	<link rel="apple-touch-icon" href="apple-touch-icon.png">
+	<link rel="icon" href="favicon.ico" type="image/x-icon"/>
+    <link rel="stylesheet" type="text/css" href="assets/css/styles.css"/>
+    <title>Goosle Search</title>
+</head>
+<body class="main">
+<?php
+if(verify_hash($opts, $auth)) {
+?>
+<div class="wrap">
+	<div class="search-box-main">
+	    <form action="results.php" method="get" autocomplete="off">
+	        <h1><span class="G">G</span>oosle</h1>
+	
+	        <input tabindex="10" type="text" class="search" name="q" autofocus required />
+
+	        <input type="hidden" name="t" value="0"/>
+	        <input type="hidden" name="a" value="<?php echo $opts->hash; ?>"/>
+	
+	        <div class="search-box-buttons">
+		        <button tabindex="20" name="t" value="0" type="submit">DuckDuckGo</button>
+		        <button tabindex="30" name="t" value="1" type="submit">Google</button>
+		        <?php if($opts->enable_image_search == "on") { ?>
+		        <button tabindex="40" name="t" value="2" type="submit">Image</button>
+		        <?php } ?>
+		        <?php if($opts->enable_torrent_search == "on") { ?>
+		        <button tabindex="50" name="t" value="9" type="submit">Torrent</button>
+		        <?php } ?>
+	        </div>
+	
+	    </form>
+	</div>
+
+	<?php if($opts->special['password_generator'] == "on") { ?>
+	<div class="password-generator">
+		<form method="get" action="./" autocomplete="off">
+			Password Generator:<br/><input class="password" type="text" name="pw" maxlength="27" value="<?php echo string_generator(); ?>" autocomplete="0" />
+		</form>
+	</div>
+	<?php } ?>
+</div>
+<?php 
+} else {
+	echo "<div class=\"auth-error\">Nope, go away!</div>";
+} 
+?>
+</body>
+</html>

+ 58 - 0
readme.md

@@ -0,0 +1,58 @@
+# Goosle
+A fast, privacy oriented meta search engine that just works.
+Kept simple so everyone can use it and to make sure it works on most (basic) webservers.
+
+Host for yourself and friends, with a access hash key. Or set up a public search website.
+
+After-all, finding things should be easy and not turn into a chore.
+
+## Features
+- Search on DuckDuckGo
+- Search on Google.com
+- Image search through Qwant
+- Search for magnet links on popular Torrent sites
+- Special searches for; Currency conversion, Dictionary, Wikipedia and php.net
+- Instant password generator on the home page
+- Randomized user-agents for to prevent profiling by search providers
+- Works on *any* hosting package that does PHP7.4 or newer
+- Optional: Access key as a very basic way to keep your server to yourself
+- Optional: Speed up repeat searches with APCu cache if your server has it
+
+What Goosle does *not* have.
+- Trackers and Cookies
+- User profiles or user controllable settings
+- Javascripts or Frameworks
+
+And yet it just works...
+
+## Requirements
+Any basic webserver/webhosting package with PHP7.4 or newer.
+Tested to work on Apache with PHP8.2.
+
+## Installation
+1. Unzip the download.
+2. Edit the config.php file with your preferences.
+3. Upload all files to your webserver, for example to the root folder of a subdomain (eg. search.example.com) or a sub-folder on your main site (eg. example.com/search/)
+4. Rename goosle.htaccess to .htaccess
+5. Load the site in your browser. If you've enabled the access hash add ?a=YOURHASH to the url.
+
+### Notes:
+- The .htaccess file has a redirect to force HTTPS as well as browser caching instructions ready to go.
+- The robots.txt has a rule to prevent all crawlers from crawling Goosle. But keep in mind that not every crawler obeys this file.
+- The access hash is NOT meant as a super secure measure and only works for surface level prying eyes.
+
+Have fun finding things!
+
+
+## Disclaimer
+Goosle started as a fork of LibreY, and ended up as a rewrite and something different completely. While the code structure remains largely the same, most functions have been rewritten or altered to work as I need it to.
+Search results take design cues from DuckDuckGo and the torrent search has been modified to show more useful information where possible.
+
+Goosle does not index, store or distribute torrent files. If you like, or found a use for, what you downloaded, you should probably buy a legal copy of it.
+
+## Support
+Goosle comes with limited support. You can post your questions on Github or on my support forum on [ajdg.solutions](https://ajdg.solutions/support/).
+
+## Changelog
+1.0 - December 5, 2023
+- Initial release

+ 74 - 0
results.php

@@ -0,0 +1,74 @@
+<?php
+require "functions/tools.php";
+require "functions/search_engine.php";
+
+$opts = load_opts();
+$auth = (isset($_GET['a'])) ? sanitize($_GET['a']) : $opts->user_auth;
+/* ------------------------------------------------------------------------------------
+*  Goosle - A meta search engine for private and fast internet fun.
+*
+*  COPYRIGHT NOTICE
+*  Copyright 2023-2024 Arnan de Gans. All Rights Reserved.
+*
+*  COPYRIGHT NOTICES AND ALL THE COMMENTS SHOULD REMAIN INTACT.
+*  By using this code you agree to indemnify Arnan de Gans from any 
+*  liability that might arise from its use.
+------------------------------------------------------------------------------------ */
+?>
+<!DOCTYPE html >
+<html lang="en">
+<head>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
+    <meta charset="UTF-8"/>
+    <meta name="description" content="A private meta search engine!"/>
+    <meta name="referrer" content="no-referrer"/>
+	<link rel="apple-touch-icon" href="apple-touch-icon.png">
+	<link rel="icon" href="favicon.ico" type="image/x-icon"/>
+    <link rel="stylesheet" type="text/css" href="assets/css/styles.css"/>
+	<title><?php echo $opts->query; ?> - Goosle Search</title>
+</head>
+<body>
+<?php
+if(verify_hash($opts, $auth)) {
+?>
+<div class="wrap">
+	<div class="header-wrap">
+		<form action="results.php" method="get" autocomplete="off">
+		    <h1 class="logo"><a class="no-decoration" href="./?a=<?php echo $opts->hash; ?>"><span class="G">G</span>oosle</a></h1>        
+		    <input tabindex="1" class="search" type="text" value="<?php echo (strlen($opts->query) > 0) ? htmlspecialchars($opts->query) : "" ; ?>" name="q" required /><input tabindex="2" class="button" type="submit" value="Search" />
+		
+	        <input type="hidden" name="t" value="<?php echo $opts->type; ?>"/>
+		    <input type="hidden" name="a" value="<?php echo $opts->hash; ?>">
+	 
+	        <div class="navigation-header">
+		        <a <?php echo ($opts->type == "0") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=0"><img src="assets/images/search.png" alt="DuckDuckGo results" />DuckDuckGo</a>
+		        <a <?php echo ($opts->type == "1") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=1"><img src="assets/images/search.png" alt="Google results" />Google</a>
+		        <?php if($opts->enable_image_search == "on") { ?>
+		        <a <?php echo ($opts->type == "2") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=2"><img src="assets/images/image.png" alt="Image results" />Image</a>
+		        <?php } ?>
+		        <?php if($opts->enable_torrent_search == "on") { ?>
+		        <a <?php echo ($opts->type == "9") ? "class=\"active\" " : ""; ?> href="./results.php?q=<?php echo urlencode($opts->query); ?>&a=<?php echo $opts->hash; ?>&t=9"><img src="assets/images/torrent.png" alt="Torrent results" />Torrent</a>
+		        <?php } ?>
+	       </div>
+		</form>
+	</div>
+	
+	<div class="results-wrap">
+    <?php fetch_search_results($opts); ?>
+	</div>
+</div>
+
+<div class="footer-wrap">
+	<div class="footer">
+		&copy; <?php echo date('Y'); ?> <a href="https://ajdg.solutions/" target="_blank">Arnan de Gans</a>. All rights reserved.
+		<span style="float:right;"><a href="./?a=<?php echo $opts->hash; ?>">Start</a> - <a href="./help.php?a=<?php echo $opts->hash; ?>">Help</a> - Your IP: <?php echo $_SERVER["REMOTE_ADDR"]; ?></span>
+	</div>
+</div>
+
+<?php 
+} else {
+	echo "<div class=\"auth-error\">Nope, go away!</div>";
+} 
+?>
+</body>
+</html>

+ 2 - 0
robots.txt

@@ -0,0 +1,2 @@
+User-agent: *
+Disallow: /