diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000..f9e5082 Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/assets/css/styles.css b/assets/css/styles.css new file mode 100644 index 0000000..ec7f01e --- /dev/null +++ b/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; } +} \ No newline at end of file diff --git a/assets/images/image.png b/assets/images/image.png new file mode 100644 index 0000000..deaf683 Binary files /dev/null and b/assets/images/image.png differ diff --git a/assets/images/search.png b/assets/images/search.png new file mode 100644 index 0000000..bdb9d50 Binary files /dev/null and b/assets/images/search.png differ diff --git a/assets/images/torrent.png b/assets/images/torrent.png new file mode 100644 index 0000000..58e1287 Binary files /dev/null and b/assets/images/torrent.png differ diff --git a/config.php b/config.php new file mode 100644 index 0000000..3c236db --- /dev/null +++ b/config.php @@ -0,0 +1,114 @@ + "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" +); +?> diff --git a/engines/duckduckgo.php b/engines/duckduckgo.php new file mode 100644 index 0000000..8c2dc93 --- /dev/null +++ b/engines/duckduckgo.php @@ -0,0 +1,93 @@ +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; + } + +} +?> diff --git a/engines/google.php b/engines/google.php new file mode 100644 index 0000000..b134a14 --- /dev/null +++ b/engines/google.php @@ -0,0 +1,112 @@ +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; + } +} +?> diff --git a/engines/search-image.php b/engines/search-image.php new file mode 100644 index 0000000..b1c3e9e --- /dev/null +++ b/engines/search-image.php @@ -0,0 +1,117 @@ +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 '
Results: ';
+			print_r($results);
+			echo '
'; + } + + echo "
"; + echo "
    "; + + // Elapsed time + if(array_key_exists("search", $results)) { + echo "
  1. Fetched the results in ".$results['time']." seconds.
  2. "; + } + + echo "
"; + + echo "
"; + echo "
    "; + + // 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 "
  1. "; + echo "\"".$result["alt"]."\""; + echo "
  2. "; + } + } + + echo "
"; + echo "
"; + echo "
Not what you're looking for? Try query)."&iax=images&ia=images\" target=\"_blank\">DuckDuckGo or query)."&tbm=isch&pws=0\" target=\"_blank\">Google Images.
"; + echo "
"; + } +} +?> \ No newline at end of file diff --git a/engines/search-torrent.php b/engines/search-torrent.php new file mode 100644 index 0000000..041269c --- /dev/null +++ b/engines/search-torrent.php @@ -0,0 +1,109 @@ +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 '
Results: ';
+			print_r($results);
+			echo '
'; + } + + echo "
"; + echo "
    "; + + // Elapsed time + echo "
  1. Fetched the results in ".$results['time']." seconds.
  2. "; + + // No results found + if(array_key_exists("error", $results)) { + echo "
  3. ".$results['error']['message']."
  4. "; + } + + // Search results + if(array_key_exists("search", $results)) { + foreach($results['search'] as $result) { + $meta = array(); + // Optional data + if(array_key_exists('quality', $result)) $meta[] = "Quality: ".$result['quality']; + if(array_key_exists('year', $result)) $meta[] = "Year: ".$result['year']; + if(array_key_exists('category', $result)) $meta[] = "Category: ".$result['category']; + if(array_key_exists('runtime', $result)) $meta[] = "Runtime: ".date('H:i', mktime(0, $result['runtime'])); + if(array_key_exists('date_added', $result)) $meta[] = "Added: ".date('M d, Y', $result['date_added']); + if(array_key_exists('url', $result)) $meta[] = "Torrent page"; + + // Put result together + echo "
  5. "; + } + } + + echo "
  6. "; + echo "Goosle does not store, index, offer or distribute torrent files."; + echo "
  7. "; + + echo "
"; + echo "
Showing 50 results, sorted by most seeders.
"; + echo "
"; + } +} +?> \ No newline at end of file diff --git a/engines/search.php b/engines/search.php new file mode 100644 index 0000000..7e57e05 --- /dev/null +++ b/engines/search.php @@ -0,0 +1,164 @@ +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 '
Results: ';
+			print_r($results);
+			echo '
'; + } + + echo "
"; + echo "
    "; + + // Elapsed time + if(array_key_exists("search", $results)) { + $number_of_results = count($results['search']); + echo "
  1. Fetched ".$number_of_results." results in ".$results['time']." seconds.
  2. "; + } + + // No results found + if(array_key_exists("error", $results)) { + echo "
  3. ".$results['error']['message']."
  4. "; + } + + // 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 = "
    Or instead search for $search_specific."; + + 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 "
  5. Did you mean $didyoumean?$specific_result
  6. "; + + unset($results['search'][0], $didyoumean, $didyoumean_url, $specific_result); + } + } + + // Special result + if(array_key_exists("special", $results)) { + echo "
  7. "; + // 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'] .= '. [...]'; + } + + // 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 = ""; + $results['special']['text'] = $special_image.$results['special']['text']; + + unset($image_specs, $width, $height, $special_image); + } + echo "

    ".$results['special']['title']."

    "; + echo "
    ".$results['special']['text']."
    "; + if(array_key_exists("source", $results['special'])) { + echo ""; + } + echo "
  8. "; + } + + // 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 "
  9. "; + } + } + + echo "
"; + echo "
"; + } +} +?> diff --git a/engines/special/currency.php b/engines/special/currency.php new file mode 100644 index 0000000..59bd6f6 --- /dev/null +++ b/engines/special/currency.php @@ -0,0 +1,55 @@ +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." + ); + } + } +} +?> diff --git a/engines/special/definition.php b/engines/special/definition.php new file mode 100644 index 0000000..9eba34f --- /dev/null +++ b/engines/special/definition.php @@ -0,0 +1,58 @@ +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']))."
    "; + foreach($definitions as $key => $def) { + $formatted_response .= "
  1. ".strip_tags(trim($def['definition']))."
  2. "; + } + $formatted_response .= "
"; + + return array( + "title" => strip_tags(trim($result['word']))." [".strip_tags(trim($result['phonetic']))."]", + "text" => $formatted_response, + "source" => strip_tags(trim($result['sourceUrls'][0])) + ); + } else { + return array( + "title" => "Whoops...", + "text" => "No definitions could be loaded. Try again later." + ); + } + } +} +?> diff --git a/engines/special/php.php b/engines/special/php.php new file mode 100644 index 0000000..9b6eb55 --- /dev/null +++ b/engines/special/php.php @@ -0,0 +1,47 @@ +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" => "

".$purpose." ".$php_versions."

".highlight_string("", 1)."

", + "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." + ); + } + } +} +?> diff --git a/engines/special/wikipedia.php b/engines/special/wikipedia.php new file mode 100644 index 0000000..ee0e0b5 --- /dev/null +++ b/engines/special/wikipedia.php @@ -0,0 +1,66 @@ +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." + ); + } + } +} +?> diff --git a/engines/torrent/1337x.php b/engines/torrent/1337x.php new file mode 100644 index 0000000..cf41202 --- /dev/null +++ b/engines/torrent/1337x.php @@ -0,0 +1,142 @@ +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; + } +} +?> \ No newline at end of file diff --git a/engines/torrent/magnetize_1337x.php b/engines/torrent/magnetize_1337x.php new file mode 100644 index 0000000..20658ac --- /dev/null +++ b/engines/torrent/magnetize_1337x.php @@ -0,0 +1,49 @@ +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") +?> diff --git a/engines/torrent/nyaa.php b/engines/torrent/nyaa.php new file mode 100644 index 0000000..bbc5018 --- /dev/null +++ b/engines/torrent/nyaa.php @@ -0,0 +1,56 @@ +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; + } +} +?> diff --git a/engines/torrent/thepiratebay.php b/engines/torrent/thepiratebay.php new file mode 100644 index 0000000..d76c965 --- /dev/null +++ b/engines/torrent/thepiratebay.php @@ -0,0 +1,115 @@ +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; + } +} +?> diff --git a/engines/torrent/yts.php b/engines/torrent/yts.php new file mode 100644 index 0000000..0c2b60e --- /dev/null +++ b/engines/torrent/yts.php @@ -0,0 +1,66 @@ +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; + } +} +?> diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..4d35d8b Binary files /dev/null and b/favicon.ico differ diff --git a/functions/search_engine.php b/functions/search_engine.php new file mode 100644 index 0000000..4006e5a --- /dev/null +++ b/functions/search_engine.php @@ -0,0 +1,188 @@ +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'].". ch)['url']."\" target=\"_blank\">Go there now."; + } + + 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; +} +?> diff --git a/functions/tools.php b/functions/tools.php new file mode 100644 index 0000000..d64d4de --- /dev/null +++ b/functions/tools.php @@ -0,0 +1,127 @@ +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('/', ' › ', 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); +} +?> \ No newline at end of file diff --git a/goosle.htaccess b/goosle.htaccess new file mode 100644 index 0000000..2215c64 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/help.php b/help.php new file mode 100644 index 0000000..84bcb89 --- /dev/null +++ b/help.php @@ -0,0 +1,129 @@ +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. +------------------------------------------------------------------------------------ */ +?> + + + + + + + + + + + <?php echo $opts->query; ?> - Goosle Search + + + +
+
+
+

Goosle

+ " name="q" required /> + + + + + +
+
+ +
+
+

DuckDuckGo features

+

DuckDuckGo is mostly language agnostic and will try to figure out on it's own what language to use.

+

Searching defaults to Moderate Safe mode. To override the safe mode prefix your search with safe:on or safe:off.
On will use 'Strict' mode, while off will disable safe searching, this may yield results that are unsuitable for work or minors.

+

Note: Search bangs are not supported and the ! to trigger them is stripped out to prevent issues.

+ +

Google features

+

Searching defaults to Moderate Safe mode. You can override safe mode by adding the prefix safe:on or safe:off to your search query.
On will use 'Strict' mode, while off will disable safe searching. this may yield results that are unsuitable for work or minors.

+

Google results are not personalized by default, using Google's option for it.

+

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 lang fr for French or lang:es 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.

+

To do a category search, prefix your search with one of the following keywords; app, book, news, shop or patent. This will tell Google to look for results in that specific category.
For example: Searching for book trainspotting will (or should) show results related to the book Trainspotting.

+ + enable_image_search == "on") { ?> +

Image Search

+

Search for images through Qwant Image Search.
The number of results is limited to 50.

+

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.

+

You can search for images in a general size by adding size:small, size:medium or size:large to your search query.

+ + +

Special Searches

+ special['currency'] == "on") { ?> +

Currency converter

+

Convert currency with a simple query.
+ For example: Search for 20 EUR in HKD or 20 USD to MXN and DuckDuckGo or Google will search for it, but also a local conversion is done in a highlighted result.

+ + + special['wikipedia'] == "on") { ?> +

Wikipedia Search

+

Prefix your search with w or wiki to search on Wikipedia for a page match. This works best for English searches as Wikipedia defaults to English.
+ For example: Searching for wiki beach ball 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.

+ + + special['phpnet'] == "on") { ?> +

PHP.net Search

+

Prefix your search with php to search on php.net for a PHP function.
+ For example: Searching for php in_array or php trim will show you a brief description, compatible PHP versions and a usage example for that function.

+ + + special['definition'] == "on") { ?> +

Word Definition

+

You can easily look up the meaning of single words. Prefix the word you want to look up with any of the following keywords; d, define, mean, meaning.
+ For example: Searching for define search will search for that on Google or DuckDuckGo, but also show the definition highlighted above the search results.

+

Note: Special Searches do not work for torrent searches.

+ + + enable_torrent_search == "on") { ?> +

Torrent Search

+

Search for anything torrent sites have on offer the direct search result should give you the magnet link.
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.

+

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.

+

Disclaimer: If you like, or found a use for, what you downloaded, you should probably buy a legal copy of it.

+ + +

Acknowledgements:
Goosle started as a fork of LibreY, and takes some design cues from DuckDuckGo.com. Goosle is created by Arnan de Gans.

+ +

Version: version; ?> / Released: released; ?>

+
+
+
+ + + +Nope, go away!"; +} +?> + + \ No newline at end of file diff --git a/index.php b/index.php new file mode 100644 index 0000000..9e12308 --- /dev/null +++ b/index.php @@ -0,0 +1,72 @@ +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. +------------------------------------------------------------------------------------ */ +?> + + + + + + + + + + + Goosle Search + + + +
+
+
+

Goosle

+ + + + + + +
+ + + enable_image_search == "on") { ?> + + + enable_torrent_search == "on") { ?> + + +
+ +
+
+ + special['password_generator'] == "on") { ?> +
+
+ Password Generator:
+
+
+ +
+Nope, go away!"; +} +?> + + \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..a03c2ff --- /dev/null +++ b/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 diff --git a/results.php b/results.php new file mode 100644 index 0000000..c35dedc --- /dev/null +++ b/results.php @@ -0,0 +1,74 @@ +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. +------------------------------------------------------------------------------------ */ +?> + + + + + + + + + + + <?php echo $opts->query; ?> - Goosle Search + + + +
+
+
+

Goosle

+ " name="q" required /> + + + + + +
+
+ +
+ +
+
+ + + +Nope, go away!"; +} +?> + + \ No newline at end of file diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000..1f53798 --- /dev/null +++ b/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: /