Explorar el Código

Version 1.1

A quality of life update - Mostly.

1.1 - December 21, 2023
- [new] API search for EZTV TV Shows.
- [new] config.default.php with default settings.
- [new] New option 'imdb_id_search' in 'special' settings in config.php.
- [new] New option 'show_zero_seeders' in config.php.
- [new] Special result and torrent redirect for IMDb IDs.
- [new] Replaced image search with Yahoo! Images.
- [new] Styled 'reset' button for search fields.
- [tweak] Removed 'raw_output' option.
- [tweak] Re-arranged results array to be more logical/easy to use.
- [tweak] Re-arranged code for results to do no double checks for search results.
- [tweak] Added more user-agents.
- [tweak] Torrent results page.
- [tweak] Sanitize scraped data earlier in the process.
- [tweak] Consistent single quotes for arrays.
- [tweak] Consistent spaces, tabs and newlines.
- [fix] Inconsistent input height for search field vs search button.
- [fix] Better check if a search is currency conversion or not.
- [fix] Typos in help.php.
Arnan de Gans hace 1 año
padre
commit
86d0d57dc8

+ 16 - 7
assets/css/styles.css

@@ -9,7 +9,7 @@
 *  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; }
+body { position: relative; margin: 0; padding: 0; font-family: Arial, Helvetica, sans-serif; color: #222; background-color: #ffffff; line-height: 1.2; }
 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; }
@@ -19,8 +19,11 @@ input, button { outline: none; }
 button { cursor: pointer; }
 p { font-size: 18px; color: #494949; }
 a { text-decoration: none; color: #4495d4; }
+small, sub, sup { padding: 5px 0; color: #666; font-size: 12px; }
+sub, sup { font-style: italic; }
 a:hover { text-decoration: underline; }
 input[type="text"]:invalid ~ input[type="submit"] { opacity: 0.5; pointer-events: none; }
+input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23777'><path d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/></svg>"); }
 
 /* Page structure */
 .wrap { min-height: 100vh; overflow: hidden; }
@@ -33,7 +36,8 @@ 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-main .search[type="search"]::-webkit-search-cancel-button { background-size: 28px 28px; height: 28px; width: 28px; background-color: #f0f6fc; }
+.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 #3C4043; border-radius: 6px; }
 .search-box-buttons button:hover { border: 1px solid #5f6368; }
 
 .password-generator { margin: 30px auto; padding: 0; }
@@ -41,8 +45,10 @@ body.main { background-color: #1f242b; color: #f0f6fc; }
 
 /* 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; }
+.header-wrap .search, .header-wrap .button { position: relative; height: 40px; color: #f0f6fc; font-size: 100%; font-weight: 400; }
+.header-wrap .search { margin: 28px 0 28px 158px; padding: 5px 5px 5px 15px; width: 580px; background-color: #1f242b; border: 1px solid #303842; border-radius: 10px 0 0 10px; }
+.header-wrap .search[type="search"]::-webkit-search-cancel-button { background-size: 20px 20px; height: 20px; width: 20px; background-color: #f0f6fc; }
+.header-wrap .button { margin: 28px 10px 28px 0; padding: 5px 20px 5px 15px; background-color: #1fa4d1; border: none; border-radius: 0 10px 10px 0; }
 
 /* Search results - Header Navigation */
 .navigation-header { margin-left: 165px; margin-bottom: 10px; }
@@ -55,7 +61,7 @@ body.main { background-color: #1f242b; color: #f0f6fc; }
 /* 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 .special-result, .main-column ol .meta { 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; }
@@ -82,7 +88,9 @@ body.main { background-color: #1f242b; color: #f0f6fc; }
 }
 .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%; }
+.main-column ol.image-grid .result .image-box img { position: absolute; object-fit: cover; width: 100%; height: 100%; border-radius: 10px; }
+.main-column ol.image-grid .result .image-box img:hover { outline: none; border-color: #3C4043; box-shadow: 0 0 10px #3C4043; }
+.main-column ol.image-grid .result span { padding: 5px 0 0 0; color: #666; font-size: 12px; }
 
 /* Special results - Main column */
 .main-column ol .special-result { background-color: #fefefe; }
@@ -102,7 +110,8 @@ body.main { background-color: #1f242b; color: #f0f6fc; }
 .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; }
+.warning { position: relative; overflow: hidden; margin: 1rem 0 1rem 0; padding: .5rem 10px; color: #db9900; background-color: #ffffe0; border: 1px solid #e6db55; border-radius: 10px; }
+.error { position: relative; overflow: hidden; margin: 1rem 0 1rem 0; padding: .5rem 10px; color: #c00; background-color: #ffebe8; border: 1px solid #c00; border-radius: 10px; }
 .auth-error { margin-top: 15%; font-size: 32px; text-align: center; color: #eaeaea; }
 
 /* Footer bar */

+ 32 - 26
config.php → config.default.php

@@ -15,6 +15,13 @@ 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.
 
+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.
+	
 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
@@ -23,24 +30,13 @@ CACHE:
 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.
+ENABLE IMAGE SEARCH:
+	Enable or disable image searches - Search results are provided by Qwant.
 	"on" (Default)
 	"off"
 
-ENABLE IMAGE SEARCH:
-	Enable or disable image searches - Search results are provided by Qwant.
+ENABLE TORRENT SEARCH:
+	Enable or disable searching for torrent downloads.
 	"on" (Default)
 	"off"
 
@@ -58,6 +54,9 @@ USER AGENTS:
 	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.
 
+SHOW ZERO SEEDERS:
+	Set to "on" to include results with 0 seeders (slow or stale downloads). Off to exclude these results.
+
 BLOCK 1337x CATEGORIES:
 	Add category IDs of 1337x categories, check /engines/torrent/1337x.php for a list of known categories.
 
@@ -75,29 +74,35 @@ TORRENT TRACKERS:
 
 return (object) array(
 	"hash" => "j9fg-i2du-er6m",
-    "cache" => "off",
-    "cache_time" => 30, // (Default: 30)
-    "hash_auth" => "off", // Default: off)
-    "raw_output" => "off", // (Default: off)
+    "hash_auth" => "off", // Default: off
+    "cache" => "off", // Default: off
+    "cache_time" => 30, // Default: 30
 
-    "enable_torrent_search" => "on", // (Default: on)
-    "enable_image_search" => "on", // (Default: on)
+    "enable_image_search" => "on", // Default: on
+    "enable_torrent_search" => "on", // Default: on
 
 	"special" => array(
 		"currency" => "on", // Currency converter
 		"definition" => "on", // Word dictionary
 		"wikipedia" => "on", // Wikipedia highlight
 		"phpnet" => "on", // PHP-dot-net highlight
+		"imdb_id_search" => "on", // Highlight IMDB IDs for tv shows, to use for torrent searches
 		"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
+		"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15) Gecko/20100101 Firefox/119.0", // macOS 10.15, Firefox 119
+		"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Gecko/20100101 Firefox/116.0", // Windows 10, Firefox 116
+		"Mozilla/5.0 (X11; Ubuntu; Linux x86_64) Gecko/20100101 Firefox/83.0", // Linux Ubuntu, Firefox 83
+		"Mozilla/5.0 (X11; Linux i686) Gecko/20100101 Firefox/119.0", // Linux Generic, Firefox 119
+		"Mozilla/5.0 (Linux; Android 5.0.2; AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/3.3 Chrome/38.0.2125.102 Safari/537.36", // Android 5, Samsungbrowser 3
+		"Mozilla/5.0 (Linux; Android 7.0; AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.116 Safari/537.36", // Android 7, Chrome 60
+		"Mozilla/5.0 (Linux; U; Android 4.2.2; he-il; AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30", // Android 4. Some webkit browser (Chrome?)
+		"Mozilla/5.0 (iPhone12,1; U; CPU iPhone OS 13_0 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) Version/10.0 Mobile/15E148 Safari/602.1", // iOS 13, Safari
+		"Mozilla/5.0 (iPhone; CPU iPhone OS 12_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/13.2b11866 Mobile/16A366 Safari/605.1.15", // iOS 12, Firefox 13
 	),
 
+    "show_zero_seeders" => "on", // Default: on
     "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"
@@ -108,6 +113,7 @@ return (object) array(
     	"udp://exodus.desync.com:6969/announce", 
     	"udp://tracker.torrent.eu.org:451/announce",
     ),
-    "version" => "1.0.2"
+
+    "version" => "1.1b2" // Please don't change this
 );
 ?>

+ 8 - 6
engines/duckduckgo.php

@@ -51,21 +51,23 @@ class DuckDuckGoRequest extends EngineRequest {
     }
 
     public function parse_results($response) {
-		$results = array();
+		$results = array("search" => array());
 		$xpath = get_xpath($response);
 
-		if(!$xpath) return $results;
+		if(!$xpath) return array();
  
+		// Scrape recommended
 		$didyoumean = $xpath->query(".//div[@id='did_you_mean']/a[1]")[0];
 		if(!is_null($didyoumean)) {
-			array_push($results, array("did_you_mean" => $didyoumean->textContent));
+			$results['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));
+			$results['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) {
+		// Scrape the results
+		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;
 
@@ -79,7 +81,7 @@ class DuckDuckGoRequest extends EngineRequest {
 
 			$description = $xpath->evaluate(".//a[@class='result__snippet']", $result)[0];
 
-            array_push($results, array (
+            array_push($results['search'], array (
                 "title" => htmlspecialchars($title->textContent),
                 "url" =>  htmlspecialchars($url->textContent),
                 "description" =>  $description == null ? 'No description was provided for this site.' : htmlspecialchars($description->textContent)

+ 7 - 5
engines/google.php

@@ -71,20 +71,22 @@ class GoogleRequest extends EngineRequest {
     }
 
     public function parse_results($response) {
-        $results = array();
+		$results = array("search" => array());
         $xpath = get_xpath($response);
 
-        if(!$xpath) return $results;
+        if(!$xpath) return array();
 
+		// Scrape recommended
         $didyoumean = $xpath->query(".//a[@class='gL9Hy']")[0];
         if(!is_null($didyoumean)) {
-            array_push($results, array("did_you_mean" => $didyoumean->textContent));
+			$results['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));
+			$results['search_specific'] = $search_specific->textContent;
         }
 
+		// Scrape the results
         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;
@@ -99,7 +101,7 @@ class GoogleRequest extends EngineRequest {
 
 			$description = $xpath->evaluate(".//div[contains(@class, 'VwiC3b')]", $result)[0];
 
-            array_push($results, array (
+            array_push($results['search'], array (
                 "title" => htmlspecialchars($title->textContent),
                 "url" =>  htmlspecialchars($url->textContent),
                 "description" =>  $description == null ? 'No description was provided for this site.' : htmlspecialchars($description->textContent)

+ 118 - 56
engines/search-image.php

@@ -1,6 +1,5 @@
 <?php
 class ImageSearch extends EngineRequest {
-
 	public function get_request_url() {
         $results = array();
 
@@ -15,8 +14,9 @@ class ImageSearch extends EngineRequest {
 			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] == "xlrg") $switch[1] = "wallpaper";
 	
-				if($switch[1] == "small" || $switch[1] == "medium" || $switch[1] == "large") {
+				if($switch[1] == "small" || $switch[1] == "medium" || $switch[1] == "large" || $switch[1] == "wallpaper") {
 					$size = $switch[1];
 				}
 				
@@ -24,12 +24,11 @@ class ImageSearch extends EngineRequest {
 			}
 		}
 
-		// q = query
-		// t = Search type (images)
-		// size = Preferred image size (small|medium|large)
+		// p = query
+		// imgsz = Image size (small|medium|large|wallpaper)
 
-		$args = array("q" => $this->query, "t" => "images", "size" => $size);
-        $url = "https://lite.qwant.com?".http_build_query($args);
+		$args = array("p" => $this->query, "imgsz" => $size);
+        $url = "https://images.search.yahoo.com/search/images?".http_build_query($args);
 
         unset($query_terms, $switch, $args, $size);
 
@@ -40,35 +39,63 @@ class ImageSearch extends EngineRequest {
 		$results = array("search" => array());
 		$xpath = get_xpath($response);
 	
-		if($xpath) {
-			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,
-					));
-				}
-			}
+		// Failed to load page
+        if(!$xpath) return array();
+
+		// Scrape recommended
+		$didyoumean = $xpath->query(".//section[@class='dym-c']/section/h3/a")[0];
+		if(!is_null($didyoumean)) {
+			$results['did_you_mean'] = $didyoumean->textContent;
 		}
+        $search_specific = $xpath->query(".//section[@class='dym-c']/section/h5/a")[0];
+        if(!is_null($search_specific)) {
+			$results['search_specific'] = $search_specific->textContent;
+        }
+		
+		// Scrape the results
+		foreach($xpath->query("//li[contains(@class, 'ld') and not(contains(@class, 'slotting'))][position() < 101]") as $result) {
+ 			$image = $xpath->evaluate(".//img/@src", $result)[0];
+            if($image == null) continue;
 
-		// Add warning if there are no results, or a text if there is no search query.
+ 			$url_data = $xpath->evaluate(".//a/@href", $result)[0];
+            if($url_data == null) continue;
+
+ 			// Get meta data
+ 			// -- Relevant $url_data (there is more, but unused by Goosle)
+			// w = Image width (1280)
+			// h = Image height (720)
+			// imgurl = Actual full size image (Used in Yahoo preview/popup)
+			// rurl = Url to page where the image is used
+			// size = Image size (413.1KB)
+			// tt = Website title (Used for image alt text)
+			parse_str($url_data->textContent, $url_data);
+
+			// filter duplicate urls/results
+            if(!empty($results['search'])) {
+		        $result_urls = array_column($results['search'], "direct_link");
+                if(in_array($url_data['imgurl'], $result_urls)) continue;
+            }
+
+			// Deal with optional or missing data
+			$dimensions_w = (!array_key_exists('w', $url_data) || empty($url_data['w'])) ? 0 : htmlspecialchars($url_data['w']);
+			$dimensions_h = (!array_key_exists('h', $url_data) || empty($url_data['h'])) ? 0 : htmlspecialchars($url_data['h']);
+			$filesize = (!array_key_exists('size', $url_data) || empty($url_data['size'])) ? "" : htmlspecialchars($url_data['size']);
+			$link = (!array_key_exists('imgurl', $url_data) || empty($url_data['imgurl'])) ? "" : "//".htmlspecialchars($url_data['imgurl']);
+
+			array_push($results['search'], array (
+				"image" => htmlspecialchars($image->textContent),
+				"alt" => htmlspecialchars($url_data['tt']),
+				"url" => htmlspecialchars($url_data['rurl']),
+				"height" => $dimensions_w,
+				"width" => $dimensions_h,
+				"filesize" => $filesize,
+				"direct_link" => $link
+			));
+		}
+
+		// Add error if there are no search results
 		if(empty($results['search'])) {
-			$results["error"] = array(
+			$results['error'] = array(
 				"message" => "No results found. Please try with less or different keywords!"
 			);
 		}
@@ -77,41 +104,76 @@ class ImageSearch extends EngineRequest {
 	}
 	
 	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>";
+/*
+		echo '<pre>Results: ';
+		print_r($results);
+		echo '</pre>';
+*/
 
-		// Elapsed time
 		if(array_key_exists("search", $results)) {
-			echo "<li class=\"meta-time\">Fetched the results in ".$results['time']." seconds.</li>";
-		}
+			echo "<ol>";
+
+			// Elapsed time
+			$number_of_results = count($results['search']);
+			echo "<li class=\"meta\">Fetched ".$number_of_results." results in ".$results['time']." seconds.</li>";
 
-        echo "</ol>";
+			// Did you mean/Search suggestion
+			if(array_key_exists("did_you_mean", $results)) {
+				$specific_result = "";
+
+				if(array_key_exists("search_specific", $results)) {
+					// Format query url
+					$search_specific = "\"".$results['search_specific']."\"";
+					$search_specific_url = "./results.php?q=".urlencode($search_specific)."&t=".$opts->type."&a=".$opts->hash;
+					
+					// Specific search
+					$specific_result = "<br /><small>Or instead search for <a href=\"".$search_specific_url."\">".$search_specific."</a>.</small>";
+		
+					unset($search_specific, $search_specific_url);
+				}
 
-		echo "<div class=\"image-wrapper\">";
-		echo "<ol class=\"image-grid\">";
+				$didyoumean_url = "./results.php?q=".urlencode($results['did_you_mean'])."&t=".$opts->type."&a=".$opts->hash;
+	
+				echo "<li class=\"meta\">Did you mean <a href=\"".$didyoumean_url."\">".$results['did_you_mean']."</a>?$specific_result</li>";
+	
+				unset($didyoumean_url, $specific_result);
+			}
+			echo "</ol>";
+
+			// Search results
+			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;
+				$meta = $links = array();
+
+				// Optional data
+				if(!empty($result['height']) && !empty($result['width'])) $meta[] = $result['width']."&times;".$result['height'];
+				if(!empty($result['filesize'])) $meta[] = $result['filesize'];
+
+				// Links
+				$links[] = "<a href=\"".$result['url']."\" target=\"_blank\">Website</a>";
+				if(!empty($result['direct_link'])) $links[] = "<a href=\"".$result['direct_link']."\" target=\"_blank\">Image</a>";
 
 				// 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 "<a href=\"".$result['url']."\" target=\"_blank\" title=\"".$result['alt']."\"><img src=\"".$result['image']."\" alt=\"".$result['alt']."\" /></a>";
+				echo "</div><span>".implode(" - ", $meta)."<br />".implode(" - ", $links)."</span>";
+				echo "</li>";
+				
+				unset($result, $meta, $links);
 	        }
+
+	        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>, <a href=\"https://images.search.yahoo.com/search/images?p=".urlencode($opts->query)."\" target=\"_blank\">Yahoo! Images</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 "</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>";
+		// No results found
+        if(array_key_exists("error", $results)) {
+            echo "<div class=\"error\">".$results['error']['message']."</div>";
+        }
+
 	}
 }
 ?>

+ 56 - 31
engines/search-torrent.php

@@ -20,19 +20,21 @@ class TorrentSearch extends EngineRequest {
 		require "engines/torrent/nyaa.php";
 		require "engines/torrent/thepiratebay.php";
 		require "engines/torrent/yts.php";
+		require "engines/torrent/eztv.php";
 		
 		$this->requests = array(
 			new LeetxRequest($opts, $mh), // 1337x
 			new NyaaRequest($opts, $mh),
 			new PirateBayRequest($opts, $mh),
-			new YTSRequest($opts, $mh)
+			new YTSRequest($opts, $mh),
+			new EZTVRequest($opts, $mh)
 		);
 	}
 
     public function parse_results($response) {
         $results = $results_temp = array();
 
-        foreach ($this->requests as $request) {
+        foreach($this->requests as $request) {
             if($request->request_successful()) {
                 $results_temp = array_merge($results_temp, $request->get_results());
             }
@@ -45,13 +47,20 @@ class TorrentSearch extends EngineRequest {
 	
 			// Cap results
 			$results['search'] = array_slice($results_temp, 0, 50);
-			unset($results_temp);
+
+			// Count results per site
+			$sources = array_count_values(array_column($results['search'], 'source'));
+			if(count($sources) > 0) $results['sources'] = $sources;
+
+			unset($sources);
 		}
 
-		// Add warning if there are no results
+		unset($results_temp);
+
+		// Add error if there are no search results
         if(empty($results)) {
-            $results["error"] = array(
-                "message" => "No results found. Please try with less or different keywords!" 
+            $results['error'] = array(
+                "message" => "No results found. Please try with more specific or different keywords!" 
             );
         }
 
@@ -59,51 +68,67 @@ class TorrentSearch extends EngineRequest {
     }
 
     public static function print_results($results, $opts) {
-		if($opts->raw_output == "on") {
-			echo '<pre>Results: ';
-			print_r($results);
-			echo '</pre>';
-		}
+/*
+		echo '<pre>Results: ';
+		print_r($results);
+		echo '</pre>';
+*/
 
-		echo "<section class=\"main-column\">";
-		echo "<ol>";
+		if(array_key_exists("search", $results)) {
+			echo "<ol>";
 
-		// Elapsed time
-		echo "<li class=\"meta-time\">Fetched the results in ".$results['time']." seconds.</li>";
+			// Format sources
+			$sources = "";
+	        if(array_key_exists("sources", $results)) {
+				$sources = array();
+				foreach($results['sources'] as $source => $amount) {
+					$plural = ($amount > 1) ? "results" : "result";
+					$sources[] = $amount." ".$plural." from ".$source;
+				}
+				$sources = implode(', ', $sources);
+	
+			    $last_comma = strrpos($sources, ', ');
+			    if($last_comma !== false) {
+			        $sources = substr_replace($sources, ' and ', $last_comma, 2);
+			    }
 
-		// No results found
-        if(array_key_exists("error", $results)) {
-            echo "<li class=\"meta-error\">".$results['error']['message']."</li>";
-        }
+				$sources = "<br /><small>Including ".$sources.". Links with the most seeders are listed first.</small>";
+			}
 
-		// Search results
-		if(array_key_exists("search", $results)) {
+			// Elapsed time
+			$number_of_results = count($results['search']);
+			echo "<li class=\"meta\">Fetched ".$number_of_results." results in ".$results['time']." seconds.".$sources."</li>";
+
+			// 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>";
+				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"]."\">".$result["source"]."</a></div>";
-				echo "<div class=\"title\"><a href=\"".$result["magnet"]."\"><h2>".stripslashes($result["name"])."</h2></a></div>";
+				echo "<div class=\"url\"><a href=\"".$result['magnet']."\">".$result['source']."</a></div>";
+				echo "<div class=\"title\"><a href=\"".$result['magnet']."\"><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>";
+
+				unset($result, $meta);
 			}
-		}
 
-		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 up to 50 results, sorted by most seeders.<br />Goosle does not index, offer or distribute torrent files.</small></center>";
+		}
 
-		echo "</ol>";
-		echo "<center><small>Showing 50 results, sorted by most seeders.</small></center>";
-		echo "</section>";
+		// No results found
+        if(array_key_exists("error", $results)) {
+            echo "<div class=\"error\">".$results['error']['message']."</div>";
+        }
 	}
 }
 ?>

+ 78 - 67
engines/search.php

@@ -36,17 +36,17 @@ class TextSearch extends EngineRequest {
         // Abort if no results from search engine
         if(!isset($this->engine_request)) return $results;
 
-		// Add search results
+		// Add search results if there are any, otherwise add error
 		if($this->engine_request->request_successful()) {
 			$search_result = $this->engine_request->get_results();
 
 			if($search_result) {
-				$results['search'] = $search_result;
+				$results = $search_result;
 			}
 
 			unset($search_result);
 		} else {
-            $results["error"] = array(
+            $results['error'] = array(
                 "message" => "Error code ".curl_getinfo($this->engine_request->ch)['http_code']." for ".curl_getinfo($this->engine_request->ch)['url'].".<br />Try again in a few seconds or <a href=\"".curl_getinfo($this->engine_request->ch)['url']."\" target=\"_blank\">visit the search engine</a> in a new tab."
             );
 		}			
@@ -62,9 +62,9 @@ class TextSearch extends EngineRequest {
 			unset($special_result);
         }
 
-		// Add warning if there are no results, or a text if there is no search query.
+		// Add error if there are no search results
 		if(empty($results)) {
-			$results["error"] = array(
+			$results['error'] = array(
 				"message" => "No results found. Please try with less or different keywords!"
 			);
 		}
@@ -73,93 +73,104 @@ class TextSearch extends EngineRequest {
     }
 
     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>";
+/*
+		echo '<pre>Results: ';
+		print_r($results);
+		echo '</pre>';
+*/
 
-		// 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>";
-		}
+			echo "<ol>";
 
-		// No results found
-        if(array_key_exists("error", $results)) {
-            echo "<li class=\"meta-error\">".$results['error']['message']."</li>";
-        }
+			// Elapsed time
+			$number_of_results = count($results['search']);
+			echo "<li class=\"meta\">Fetched ".$number_of_results." results in ".$results['time']." seconds.</li>";
 
-		// Did you mean/Search suggestion
-		if(array_key_exists("search", $results)) {
-			$specific_result = "";
+			// Did you mean/Search suggestion
+			if(array_key_exists("did_you_mean", $results)) {
+				$specific_result = "";
 
-			if(array_key_exists("did_you_mean", $results['search'][0])) {
-				if(array_key_exists("search_specific", $results['search'][1])) {
+				if(array_key_exists("search_specific", $results)) {
 					// Add double quotes to Google search
-					$search_specific = ($opts->type == 1) ? "\"".$results['search'][1]['search_specific']."\"" : $results['search'][1]['search_specific'];
+					$search_specific = ($opts->type == 1) ? "\"".$results['search_specific']."\"" : $results['search_specific'];
+
+					// Format query url
 					$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>";
+					
+					// Specific search
+					$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);
+					unset($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;
+				$didyoumean_url = "./results.php?q="  . urlencode($results['did_you_mean'])."&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>";
+				echo "<li class=\"meta\">Did you mean <a href=\"".$didyoumean_url."\">".$results['did_you_mean']."</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>';
+				unset($didyoumean_url, $specific_result);
 			}
 
-			// 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);
+			// Special results
+			if($opts->special['imdb_id_search'] == "on") {
+				$found = false;
+				foreach($results['search'] as $search_result) {
+					if(!$found && preg_match_all("/(imdb.com|tt[0-9]+)/i", $search_result['url'], $imdb_result) && stristr($search_result['title'], "tv series") !== false) {
+						$results['special'] = array(
+							"title" => $search_result['title'], 
+							"text" => "Goosle found an IMDb ID for this TV Show in your results (".$imdb_result[0][1].") - <a href=\"./results.php?q=".$imdb_result[0][1]."&a=".$opts->hash."&t=9\">search for magnet links</a>?<br /><sub>An IMDb ID is detected when a TV Show is present in the results. The first match is highlighted here.</sub>"
+						);
+						$found = true;
+					}
+				}
 			}
-			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>";
+			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>";
 			}
-			echo "</article></li>";
-		}
 
-		// Search results
-		if(array_key_exists("search", $results)) {
+			// 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 "<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>";
+
+				unset($result);
 	        }
+
+			echo "</ol>";
 		}
- 
-        echo "</ol>";
-        echo "</section>";
+
+		// No results found
+        if(array_key_exists("error", $results)) {
+            echo "<div class=\"error\">".$results['error']['message']."</div>";
+        }
     }
 }
 ?>

+ 1 - 1
engines/special/currency.php

@@ -18,7 +18,7 @@ class CurrencyRequest extends EngineRequest {
         $json_response = json_decode($response, true);
 
 		if(!empty($json_response)) {
-	        $result = $json_response["rates"];
+	        $result = $json_response['rates'];
 
 			// Process query
 			// [0] = AMOUNT

+ 1 - 1
engines/special/wikipedia.php

@@ -51,7 +51,7 @@ class WikipediaRequest extends EngineRequest {
 			);
 			
 			if (array_key_exists("thumbnail",  $result)) {
-				$response["image"] = strip_tags(trim($result["thumbnail"]["source"]));
+				$response['image'] = strip_tags(trim($result['thumbnail']['source']));
 			}
 			
 			return $response;

+ 38 - 33
engines/torrent/1337x.php

@@ -10,17 +10,17 @@
 *  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;
-
+	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);
+		
+		// Failed to load page
+		if(!$xpath) return $results;
+		
 		$categories = array(
 			1 => "DVD",
 			2 => "Divx/Xvid",
@@ -105,38 +105,43 @@ class LeetxRequest extends EngineRequest {
 		);
 
 		// 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;
+		foreach($xpath->query("//table/tbody/tr") as $result) {
+			$name = sanitize($xpath->evaluate(".//td[@class='coll-1 name']/a", $result)[1]->textContent);
+			$seeders = sanitize($xpath->evaluate(".//td[@class='coll-2 seeds']", $result)[0]->textContent);
+			$leechers = sanitize($xpath->evaluate(".//td[@class='coll-3 leeches']", $result)[0]->textContent);
+			$size_unformatted = explode(" ", sanitize($xpath->evaluate(".//td[contains(@class, 'coll-4 size')]", $result)[0]->textContent));
+			$size = $size_unformatted[0] . " " . preg_replace("/[0-9]+/", "", $size_unformatted[1]);
+			
+			$category = explode("/", sanitize($xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[0]->textContent));
+			$category = $category[2];
+			$url = "https://1337x.to".sanitize($xpath->evaluate(".//td[@class='coll-1 name']/a/@href", $result)[1]->textContent);
 			$date_added = explode(" ", sanitize($xpath->evaluate(".//td[@class='coll-date']", $result)[0]->textContent));
 			$date_added = mktime(0, 0, 0, intval(date("m", strtotime($date_added[0]))), intval(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]);
+			
+			// Block these categories
+			if(in_array($category, $this->opts->leetx_categories_blocked)) continue;
+			
+			// Remove results with 0 seeders?
+			if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
 			
 			array_push($results, array (
-                // Required
+				// Required
 				"source" => "1337x.to",
-				"name" => sanitize($name),
+				"name" => $name,
 				"magnet" => "./engines/torrent/magnetize_1337x.php?url=".$url,
-				"seeders" => sanitize($seeders),
-				"leechers" => sanitize($leechers),
-				"size" => sanitize($size),
+				"seeders" => $seeders,
+				"leechers" => $leechers,
+				"size" => $size,
 				// Optional values
-				"category" => $categories[sanitize($category[2])],
+				"category" => $categories[$category],
 				"url" => $url,
 				"date_added" => $date_added
 			));
-        }
 
-        return $results;
-    }
+			unset($name, $seeders, $leechers, $size_unformatted, $size, $category, $url, $date_added);
+		}
+		
+		return $results;
+	}
 }
 ?>

+ 88 - 0
engines/torrent/eztv.php

@@ -0,0 +1,88 @@
+<?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 EZTVRequest extends EngineRequest {
+	public function get_request_url() {
+		// Make reasonably sure it's an IMDb id and abort if it's not
+		$query_terms = explode(" ", $this->query);
+		if(substr(strtolower($query_terms[0]), 0, 2) !== "tt") return "";
+		
+		// Prepare a search query by stripping out everything but numbers and abort if nothing is left
+		$query = preg_replace('/[^0-9]/', '', $query_terms[0]);
+		if(strlen($query) == 0) return "";
+		
+		// Is eztvx.to blocked for you? Use one of these urls as an alternative
+		// eztv1.xyz, eztv.wf, eztv.tf, eztv.yt
+		return "https://eztvx.to/api/get-torrents?imdb_id=".urlencode($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['torrents_count'] == 0) return $results;
+		
+		// Use API result
+		foreach ($json_response['torrents'] as $episode) {
+			$name = sanitize($episode['title']);
+			$magnet = sanitize($episode['magnet_url']);
+			$seeders = sanitize($episode['seeds']);
+			$leechers = sanitize($episode['peers']);
+			$size = sanitize($episode['size_bytes']);
+			
+			// Find actual quality of episode
+			if(preg_match('/(480p|720p|1080p|2160p)/i', $name, $quality)) {
+				$quality = $quality[0];
+			} else {
+				$quality = "Unknown";
+			}
+			
+			$date_added = sanitize($episode['date_released_unix']);
+			
+			// Filter by Season (S01) or Season and Episode (S01E01)
+			$season = sanitize($episode['season']);
+			$episode = sanitize($episode['episode']);
+			
+			if(preg_match_all("/(S[0-9]{1,3}|E[0-9]{1,3})/i", $this->query, $filter_episode)) {
+				if(str_ireplace("s0", "", $filter_episode[0][0]) != $season || (array_key_exists(1, $filter_episode[1]) && str_ireplace("e0", "", $filter_episode[0][1]) != $episode)) {
+					continue;
+				}
+			}
+			
+			// Remove results with 0 seeders?
+			if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
+			
+			array_push($results, array (
+				// Required
+				"source" => "EZTV",
+				"name" => $name,
+				"magnet" => $magnet,
+				"seeders" => $seeders,
+				"leechers" => $leechers,
+				"size" => human_filesize($size),
+				// Optional
+				"quality" => $quality,
+				"date_added" => $date_added
+			));
+
+			unset($name, $magnet, $seeders, $leechers, $size, $quality, $category, $date_added, $season, $episode);
+		}
+		
+		return $results;
+	}
+}
+?>

+ 44 - 37
engines/torrent/nyaa.php

@@ -10,47 +10,54 @@
 *  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;
-
+	public function get_request_url() {
+		return "https://nyaa.si/?q=".urlencode($this->query);
+	}
+	
+	public function parse_results($response) {
+		$results = array();
+		$xpath = get_xpath($response);
+		
+		// Failed 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));
+		foreach($xpath->query("//tbody/tr") as $result) {
+			$meta = $xpath->evaluate(".//td[@class='text-center']", $result);
+			
+			$name = sanitize($xpath->evaluate(".//td[@colspan='2']//a[not(contains(@class, 'comments'))]/@title", $result)[0]->textContent);
+			$magnet = sanitize($xpath->evaluate(".//a[2]/@href", $meta[0])[0]->textContent);
+			$seeders = sanitize($meta[3]->textContent);
+			$leechers = sanitize($meta[4]->textContent);
+			$size =  sanitize($meta[1]->textContent);
+			
+			$category = sanitize($xpath->evaluate(".//td[1]//a/@title", $result)[0]->textContent);
+			$url = sanitize($xpath->evaluate(".//td[@colspan='2']//a[not(contains(@class, 'comments'))]/@href", $result)[0]->textContent);
+			$date_added =  sanitize($meta[2]->textContent);
+			$date_added = explode("-", substr($date_added, 0, 10));
 			$date_added = mktime(0, 0, 0, intval($date_added[1]), intval($date_added[2]), intval($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),
+			
+			// Remove results with 0 seeders?
+			if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
+			
+			array_push($results, array (
+				// Required
+				"source" => "nyaa.si",
+				"name" => $name,
+				"magnet" => $magnet,
+				"seeders" => $seeders,
+				"leechers" => $leechers,
+				"size" => $size,
 				// Optional values
-				"category" => str_replace(" - ", "/", sanitize($category)),
-                "url" => "https://nyaa.si".sanitize($url),
+				"category" => str_replace(" - ", "/", $category),
+				"url" => "https://nyaa.si".$url,
 				"date_added" => $date_added
-            ));
-        }
+			));
+
+			unset($name, $magnet, $seeders, $leechers, $size, $category, $url, $date_added, $meta);
+		}
 
-        return $results;
-    }
+		return $results;
+	}
 }
 ?>

+ 45 - 33
engines/torrent/thepiratebay.php

@@ -10,17 +10,17 @@
 *  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);
+	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;
-
+		if(empty($json_response)) return $results;
+		
 		$categories = array(
 			100 => "Audio",
 			101 => "Music",
@@ -84,32 +84,44 @@ class PirateBayRequest extends EngineRequest {
 		);
 
 		// Use API result
-        foreach($json_response as $response) {
+		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"])),
+			if($response['name'] == "No results returned") break;
+			
+			$name = sanitize($response['name']);
+			$magnet = "magnet:?xt=urn:btih:".sanitize($response['info_hash'])."&dn=".$name."&tr=".implode("&tr=", $this->opts->torrent_trackers);
+			$seeders = sanitize($response['seeders']);
+			$leechers = sanitize($response['leechers']);
+			$size = sanitize($response['size']);
+			
+			$category = sanitize($response['category']);
+			$id = sanitize($response['id']);
+			$date_added = sanitize($response['added']);
+			
+			// Block these categories
+			if(in_array($category, $this->opts->piratebay_categories_blocked)) continue;
+			
+			// Remove results with 0 seeders?
+			if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
+			
+			array_push($results, array(
+				// Required
+				"source" => "thepiratebay.org",
+				"name" => $name,
+				"magnet" => $magnet,
+				"seeders" => $seeders,
+				"leechers" => $leechers,
+				"size" => human_filesize($size),
 				// Optional
-				"category" => $categories[sanitize($response["category"])],
-                "url" => "https://thepiratebay.org/description.php?id=".sanitize($response["id"]),
- 				"date_added" => sanitize($response["added"]),
-           ));
-        }
+				"category" => $categories[$category],
+				"url" => "https://thepiratebay.org/description.php?id=".$id,
+				"date_added" => $date_added,
+ 			));
+
+		   	unset($name, $magnet, $seeders, $leechers, $size, $category, $id, $date_added);
+		}
 
-        return $results;
-    }
+		return $results;
+	}
 }
 ?>

+ 56 - 38
engines/torrent/yts.php

@@ -10,55 +10,73 @@
 *  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);
+	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;
-
+		if(empty($json_response)) return $results;
+		
 		// Nothing found
-        if($json_response["status"] != "ok" || $json_response["data"]["movie_count"] == 0) return $results;
-
+		if($json_response['status'] != "ok" || $json_response['data']['movie_count'] == 0) return $results;
+		
 		// Use API result
-        foreach ($json_response["data"]["movies"] as $movie) {
+		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;
+			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']);
+			
+			$year = sanitize($movie['year']);
+			$category = sanitize(implode(', ', $movie['genres']));
+			$runtime = sanitize($movie['runtime']);
+			$url = sanitize($movie['url']);
+			$date_added = sanitize($movie['date_uploaded_unix']);
 
-            $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
+			foreach ($movie['torrents'] as $torrent) {
+				$magnet = "magnet:?xt=urn:btih:".sanitize($torrent['hash'])."&dn=".urlencode($name)."&tr=".implode("&tr=", $this->opts->torrent_trackers);
+				$seeders = sanitize($torrent['seeds']);
+				$leechers = sanitize($torrent['peers']);
+				$size = sanitize($torrent['size']);
+				
+				$quality = sanitize($torrent['quality']);
+				
+				// Remove results with 0 seeders?
+				if($this->opts->show_zero_seeders == "off" AND $seeders == 0) continue;
+				
+				array_push($results, array (
+					// Required
 					"source" => "yts.mx",
 					"name" => $name,
 					"magnet" => $magnet,
-					"seeders" => sanitize($torrent["seeds"]),
-					"leechers" => sanitize($torrent["peers"]),
-					"size" => sanitize($torrent["size"]),
+					"seeders" => $seeders,
+					"leechers" => $leechers,
+					"size" => $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"])
+					"quality" => $quality,
+					"year" => $year,
+					"category" => $category,
+					"runtime" => $runtime,
+					"url" => $url,
+					"date_added" => $date_added
 				));
-            }
-        }
+
+				unset($magnet, $seeders, $leechers, $size, $quality);
+			}
+
+			unset($name, $year, $category, $runtime, $url, $date_added);
+		}
 
 		return $results;
 	}

+ 51 - 42
functions/search_engine.php

@@ -75,11 +75,13 @@ abstract class EngineRequest {
 	--------------------------------------*/
 	public function get_results() {
 
+		// It's a torrent search?
 		if(!isset($this->url)) return $this->parse_results(null);
 
-		// Skip if there is a cached result (from earlier search)
+		// If there is a cached result from an earlier search use that instead
 		if($this->opts->cache == "on" && has_cached_results($this->url, $this->opts->hash)) return fetch_cached_results($this->url, $this->opts->hash);
-	
+
+		// Curl request
 		if(!isset($this->ch)) return $this->parse_results(null);
 		$response = ($this->mh) ? curl_multi_getcontent($this->ch) : curl_exec($this->ch);
 
@@ -98,17 +100,17 @@ abstract class EngineRequest {
 // 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"]) : "";
-
+	$opts = require "config.php";
+	
+	// From the url/request	
+	$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;
+	if(substr($opts->query, 0, 1) == "!") $opts->query = substr($opts->query, 1);
+	
+	return $opts;
 }
 
 /*--------------------------------------
@@ -117,37 +119,44 @@ function load_opts() {
 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);
+	echo "<section class=\"main-column\">";
 
-    $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);
+	if(!empty($opts->query)) {
+		// 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, '.', '');
+	
+		// Echoes results and special searches
+	    $search->print_results($results, $opts);
+	} else {
+		echo "<div class=\"warning\">Search query can not be empty!<br />Not sure what went wrong? Learn more about <a href=\"./help.php?a=".$opts->hash."\">how to use Goosle</a>.</div>";
+    }
 
-    return $results;
+	echo "</section>";
 }
 
 /*--------------------------------------
@@ -158,7 +167,7 @@ function special_search_request($opts) {
     $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')) {
+	if($opts->special['currency'] == "on" && count($query_terms) == 4 && (is_numeric($query_terms[0]) && ($query_terms[2] == 'to' || $query_terms[2] == 'in'))) {
         require "engines/special/currency.php";
         $special_request = new CurrencyRequest($opts, null);
 	}

+ 17 - 17
functions/tools.php

@@ -20,35 +20,35 @@ function verify_hash($opts, $auth) {
 }
 
 /*--------------------------------------
-// Strip all extras from an url
+// Load pages into a DOM
 --------------------------------------*/
-function get_base_url($url) {
-    $parsed = parse_url($url);
+function get_xpath($response) {
+    if(!$response)
+        return null;
 
-    return $parsed["scheme"] . "://" . $parsed["host"] . "/";
+    $htmlDom = new DOMDocument;
+    @$htmlDom->loadHTML($response);
+    $xpath = new DOMXPath($htmlDom);
+
+    return $xpath;
 }
 
 /*--------------------------------------
-// Format search result urls
+// Strip all extras from an url
 --------------------------------------*/
-function get_formatted_url($url) {
-    $parsed = parse_url($url);
+function get_base_url($url) {
+    $url = parse_url($url);
 
-    return $parsed["scheme"] . "://" . $parsed["host"] . str_replace('/', ' &rsaquo; ', str_replace('%20', ' ', rtrim($parsed['path'], '/')));
+    return $url['scheme'] . "://" . $url['host'] . "/";
 }
 
 /*--------------------------------------
-// Load pages into a DOM
+// Format search result urls
 --------------------------------------*/
-function get_xpath($response) {
-    if(!$response)
-        return null;
-
-    $htmlDom = new DOMDocument;
-    @$htmlDom->loadHTML($response);
-    $xpath = new DOMXPath($htmlDom);
+function get_formatted_url($url) {
+    $url = parse_url($url);
 
-    return $xpath;
+    return $url['scheme'] . "://" . $url['host'] . str_replace('/', ' &rsaquo; ', str_replace('%20', ' ', rtrim($url['path'], '/')));
 }
 
 /*--------------------------------------

+ 19 - 10
help.php

@@ -35,7 +35,7 @@ if(verify_hash($opts, $auth)) {
 	<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 tabindex="1" class="search" type="search" value="<?php echo (strlen($opts->query) > 0) ? htmlspecialchars($opts->query) : "" ; ?>" name="q" /><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; ?>">
@@ -63,12 +63,12 @@ if(verify_hash($opts, $auth)) {
 			<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>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>Search for images through Yahoo! Image Search.<br />The number of results is limited to 100. So up to 100 images may appear.</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 } ?>
@@ -77,7 +77,7 @@ if(verify_hash($opts, $auth)) {
 			<?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>
+				For example: Search for <strong>20 EUR in HKD</strong> or <strong>14 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") { ?>
@@ -89,24 +89,33 @@ if(verify_hash($opts, $auth)) {
 			<?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>
+				For example: Searching for <strong>php in_array</strong> or <strong>php trim</strong> will show you a brief description, compatible PHP versions and the basic syntax 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>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> or <strong>meaning</strong>.<br />
+				For example: Searching for <strong>define search</strong> will search for that on Google or DuckDuckGo, but also show the dictionary 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>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, EZTV 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 if($opts->special['imdb_id_search'] == "on") { ?>
+					<h3>Searching TV Shows</h3>
+					<p>To do a specific search on The Pirate Bay and EZTV you can search for IMDb Title IDs. These are numeric IDs prefixed with <strong>tt</strong>. This kind of search is useful if you're looking a tv show that doesn't have a unique name.</p>
+					<p>	If you already know the Title ID you can enter it directly in the Torrent search as your search query.<br />
+					If you don't know the Title ID you can search DuckDuckGo or Google for <strong>imdb [tv show name]</strong>, for example <strong>imdb game of thrones</strong>.<br />
+					Goosle will detect the IMDb ID from the search results and show a special search result that offers you to search for Magnet Links through a torrent search.</p>
+					
+					<p>To help you narrow down seasons and episodes you can search for <strong>tt0944947 S08</strong> and get filtered results from EZTV for Game of Thrones Season 8. Searching for <strong>tt0944947 S08E05</strong> should find Season 8 Episode 5 and so on.</p>
+				<?php } ?>
+				<p><em><strong>Note:</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>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> with the intent to make search fun again.</small></p>
 		</section>
 	</div>
 </div>

+ 1 - 1
index.php

@@ -36,7 +36,7 @@ if(verify_hash($opts, $auth)) {
 	    <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 tabindex="10" type="search" class="search" name="q" autofocus />
 
 	        <input type="hidden" name="t" value="0"/>
 	        <input type="hidden" name="a" value="<?php echo $opts->hash; ?>"/>

+ 38 - 14
readme.md

@@ -1,5 +1,5 @@
 # Goosle
-A fast, privacy oriented meta search engine that just works.
+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.
@@ -11,7 +11,7 @@ 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
+- Image search through Yahoo! Images
 - 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
@@ -27,22 +27,25 @@ What Goosle does *not* have.
 
 And yet it just works...
 
+If you like Goosle, or found a use for it, please support my work and [donate](https://www.arnan.me/donate.html?mtm_campaign=goosle_readme).
+
 ## Screenshots
 [![Goosle Mainpage](https://ajdg.solutions/wp-content/uploads/2023/12/goosle-mainpage-150x150.png)](https://ajdg.solutions/wp-content/uploads/2023/12/goosle-mainpage.png)
 [![Goosle Search results](https://ajdg.solutions/wp-content/uploads/2023/12/goosle-search-150x150.png)](https://ajdg.solutions/wp-content/uploads/2023/12/goosle-search.png)
 [![Goosle Torrent results](https://ajdg.solutions/wp-content/uploads/2023/12/goosle-torrentsearch-150x150.png)](https://ajdg.solutions/wp-content/uploads/2023/12/goosle-torrentsearch.png)
 
 ## Requirements
-Any basic webserver/webhosting package with PHP7.4 or newer.
+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.
-6. Let me know where you installed Goosle :-)
+2. In the main directory. Copy config.default.php to config.php.
+3. Edit config.php file and set your preferences.
+4. 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/)
+5. Rename goosle.htaccess to .htaccess
+6. Load the site in your browser. If you've enabled the access hash add ?a=YOURHASH to the url.
+7. Let me know where you installed Goosle :-)
 
 ### Notes:
 - The .htaccess file has a redirect to force HTTPS as well as browser caching instructions ready to go.
@@ -51,25 +54,46 @@ Tested to work on Apache with PHP8.2.
 
 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 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.
 
-THe name Goosle is my last name with an L added in. Translate it from Dutch. Not in any way a derivation of Google and DuckDuckGo combined :wink:.
+The name Goosle is my last name with an L added in. Translate it from Dutch. Not in any way a derivation of Google and DuckDuckGo combined :wink:
 
 ## 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/).
+Goosle comes with limited support. \
+You can post your questions on Github or on my support forum on [ajdg.solutions](https://ajdg.solutions/support/?mtm_campaign=goosle_readme). \
+Or say hi on [Telegram](https://t.me/arnandegans).
 
 ## Changelog
+1.1 - December 21, 2023
+- [new] API search for EZTV TV Shows.
+- [new] config.default.php with default settings.
+- [new] New option 'imdb_id_search' in 'special' settings in config.php.
+- [new] New option 'show_zero_seeders' in config.php.
+- [new] Special result and torrent redirect for IMDb IDs.
+- [new] Replaced image search with Yahoo! Images.
+- [new] Styled 'reset' button for search fields.
+- [tweak] Removed 'raw_output' option.
+- [tweak] Re-arranged results array to be more logical/easy to use.
+- [tweak] Re-arranged code for results to do no double checks for search results.
+- [tweak] Added more user-agents.
+- [tweak] Torrent results page.
+- [tweak] Sanitize scraped data earlier in the process.
+- [tweak] Consistent single quotes for arrays.
+- [tweak] Consistent spaces, tabs and newlines.
+- [fix] Inconsistent input height for search field vs search button.
+- [fix] Better check if a search is currency conversion or not.
+- [fix] Typos in help.php.
+
 1.0.2 - December 7, 2023
-- [fix] Magnet links for torrents no longer opening in new tabs.
 - [change] More useful error response when search doesn't work.
 - [change] EngineRequest::request_successful() now provides a boolean response.
 - [change] Removed versioning indicator from help page.
 - [change] Added version indicator to results.php and help.php footer.
 - [change] 'Nope, Go away!' for unauthorized users changed to 'Goosle'.
+- [fix] Magnet links for torrents no longer opening in new tabs.
 
 1.0.1 - December 5, 2023
 - [fix] mktime() getting intermittent strings in 1337x crawler.

+ 3 - 2
results.php

@@ -35,8 +35,9 @@ if(verify_hash($opts, $auth)) {
 	<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 tabindex="1" class="search" type="search" value="<?php echo (strlen($opts->query) > 0) ? htmlspecialchars($opts->query) : "" ; ?>" name="q" /><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; ?>">